Commit 897bf9e6 authored by jcfr's avatar jcfr

ENH: Add SSL support

Having the application interacting with services or downloading resources
served over https protocol is now required. More particularly,
it became necessary to access resources stored on Github (extension
manager - issue #2354) or to download python packages from pip server
at run time.

The CMake option "Slicer_USE_PYTHONQT_WITH_OPENSSL" allowing to enable or
disable OpenSSL support has been introduced is set to OFF by default.

Building OpenSSL
================

On Windows, since Perl is required, OpenSSL binaries have been
generated and uploaded on kitware packages server. See [1]

The project allowing to re-generate the Windows OpenSSL archive is currently
hosted here [2].

On unix-like system, OpenSSL is built has part of the Superbuild. To ensure
is can be built in parallel, a patch is also applied [3].

Finally, since python is built with OpenSSL support, the python launcher
is now configured on all platform so that python can properly load the built
OpenSSL shared library when it is used to build project like numpy.

[1] http://packages.kitware.com/packages/application/view?applicationId=20
[2] https://gist.github.com/jcfr/6030240
[3] https://raw.github.com/Alexpux/Qt-builds/master/patches/openssl/openssl-1.0.1-parallel-build.patch

OpenSSL support in Qt, python, curl
===================================

Practically, it means that library like Qt, python, curl need to be
built with OpenSSL support.

This commit only address OpenSSL support for Python and Qt.

Curl support is not yet enabled.

For Qt OpenSSL support, Qt has to be built with the flags:
  -openssl
  -I /path/to/OpenSSL/include
  -L /path/to/OpenSSL/lib

On unix-like system, available binaries are already OpenSSL ready, Qt
has to be explicitly built only on windows. On windows, Qt will have to
be built.

Certificates
============

In case X.509v3 certificates are available on the system where Slicer is
executed, a certificate bundle "Slicer.crt" is also provided. The set of
scripts allowing to generate the bundle are provided by BLFS
(Beyond Linux From Scratch).
More details in Base/QTCore/Resources/Certs/README

Application integration
=======================

The Slicer core application will load the Slicer.crt bundle if needed.

qSlicerExtensionsInstallWidget now derives from a re-usable widget
named qSlicerWebWidget.

qSlicerSslTest has been added to check that "https" connection can
be established using Qt library.

Fixes #2998
Fixes #2355
Fixes #2354

git-svn-id: http://svn.slicer.org/Slicer4/trunk@22220 3bd1e089-480b-0410-8dfb-8563597acbee
parent ca8180af
Overview
========
This folder contains "Slicer.crt" file bundling together X.509v3 certificates (*.pem). Each one
of these X.509v3 certificate identifies a Certificate authority.
When establishing a connection with a website served over https, this one will present
a digitial certificate allowing to confirm the "digitial identity" of its associated public key
later used to establish the secured connection [1].
This digital certificate could either be signed or self-signed [2]. By default, the browser
rejects self-signed certificates and accepts only the one signed by a trusted Certificate authority.
(1) http://en.wikipedia.org/wiki/Public_key_certificate
(2) http://en.wikipedia.org/wiki/Self-signed_certificate
Certificate authority
=====================
Certificate authority (CA), is an entity that issues digital certificates. The digital certificate
certifies the ownership of a public key by the named subject of the certificate. This allows others
(relying parties) to rely upon signatures or assertions made by the private key that corresponds to
the public key that is certified.
Source: https://en.wikipedia.org/wiki/Certificate_authority
Certificate Bundle generation
=============================
Re-using the scripts provided by BLFS (Beyond Linux From Scratch) [3], the "Slicer.crt" can easily
be (re-)generated.
Step1: Download the list of trusted CAs from mozilla website [4]
certhost='http://mxr.mozilla.org' &&
certdir='/mozilla/source/security/nss/lib/ckfw/builtins' &&
url="$certhost$certdir/certdata.txt?raw=1" &&
wget --output-document certdata.txt $url &&
unset certhost certdir url
Step2: Generate "Slicer.crt"
./make-ca.sh &&
./remove-expired-certs.sh certs &&
REVISION=$(grep CVS_ID certdata.txt | cut -f4 -d'$') &&
VERSION=$(echo $REVISION | cut -f2 -d" ") &&
mv "BLFS-ca-bundle-${VERSION}.crt" "Slicer.crt" &&
unset REVISION VERSION
Step3: Clean-up
rm -rf certs &&
rm certdata.txt
Slicer and certificates
=======================
Within Slicer, secured http connection could be established using:
1) QWebView:
w = slicer.qSlicerWebWidget()
w.show()
v = w.webView()
v.setUrl(qt.QUrl("https://www.eff.org/https-everywhere"))
2) QNetworkManager (See qSlicerSslTest.cxx)
3) Python libraries like urllib
4) curl [Not yet supported]
This diff is collapsed.
#!/bin/bash
# Begin make-ca.sh
# Script to populate OpenSSL's CApath from a bundle of PEM formatted CAs
#
# The file certdata.txt must exist in the local directory
# Version number is obtained from the version of the data.
#
# Authors: DJ Lucas
# Bruce Dubbs
#
# Version 20120211
certdata="certdata.txt"
if [ ! -r $certdata ]; then
echo "$certdata must be in the local directory"
exit 1
fi
REVISION=$(grep CVS_ID $certdata | cut -f4 -d'$')
if [ -z "${REVISION}" ]; then
echo "$certfile has no 'Revision' in CVS_ID"
exit 1
fi
VERSION=$(echo $REVISION | cut -f2 -d" ")
TEMPDIR=$(mktemp -d)
TRUSTATTRIBUTES="CKA_TRUST_SERVER_AUTH"
BUNDLE="BLFS-ca-bundle-${VERSION}.crt"
CONVERTSCRIPT="make-cert.pl"
SSLDIR="/etc/ssl"
mkdir "${TEMPDIR}/certs"
# Get a list of staring lines for each cert
CERTBEGINLIST=$(grep -n "^# Certificate" "${certdata}" | cut -d ":" -f1)
# Get a list of ending lines for each cert
CERTENDLIST=`grep -n "^CKA_TRUST_STEP_UP_APPROVED" "${certdata}" | cut -d ":" -f 1`
# Start a loop
for certbegin in ${CERTBEGINLIST}; do
for certend in ${CERTENDLIST}; do
if test "${certend}" -gt "${certbegin}"; then
break
fi
done
# Dump to a temp file with the name of the file as the beginning line number
sed -n "${certbegin},${certend}p" "${certdata}" > "${TEMPDIR}/certs/${certbegin}.tmp"
done
unset CERTBEGINLIST CERTDATA CERTENDLIST certebegin certend
mkdir -p certs
rm certs/* # Make sure the directory is clean
for tempfile in ${TEMPDIR}/certs/*.tmp; do
# Make sure that the cert is trusted...
grep "CKA_TRUST_SERVER_AUTH" "${tempfile}" | \
egrep "TRUST_UNKNOWN|NOT_TRUSTED" > /dev/null
if test "${?}" = "0"; then
# Throw a meaningful error and remove the file
cp "${tempfile}" tempfile.cer
perl ${CONVERTSCRIPT} > tempfile.crt
keyhash=$(openssl x509 -noout -in tempfile.crt -hash)
echo "Certificate ${keyhash} is not trusted! Removing..."
rm -f tempfile.cer tempfile.crt "${tempfile}"
continue
fi
# If execution made it to here in the loop, the temp cert is trusted
# Find the cert data and generate a cert file for it
cp "${tempfile}" tempfile.cer
perl ${CONVERTSCRIPT} > tempfile.crt
keyhash=$(openssl x509 -noout -in tempfile.crt -hash)
mv tempfile.crt "certs/${keyhash}.pem"
rm -f tempfile.cer "${tempfile}"
echo "Created ${keyhash}.pem"
done
# Remove blacklisted files
# MD5 Collision Proof of Concept CA
if test -f certs/8f111d69.pem; then
echo "Certificate 8f111d69 is not trusted! Removing..."
rm -f certs/8f111d69.pem
fi
# Finally, generate the bundle and clean up.
cat certs/*.pem > ${BUNDLE}
rm -r "${TEMPDIR}"
#!/usr/bin/perl -w
# Used to generate PEM encoded files from Mozilla certdata.txt.
# Run as ./mkcrt.pl > certificate.crt
#
# Parts of this script courtesy of RedHat (mkcabundle.pl)
#
# This script modified for use with single file data (tempfile.cer) extracted
# from certdata.txt, taken from the latest version in the Mozilla NSS source.
# mozilla/security/nss/lib/ckfw/builtins/certdata.txt
#
# Authors: DJ Lucas
# Bruce Dubbs
#
# Version 20120211
my $certdata = './tempfile.cer';
open( IN, "cat $certdata|" )
|| die "could not open $certdata";
my $incert = 0;
while ( <IN> )
{
if ( /^CKA_VALUE MULTILINE_OCTAL/ )
{
$incert = 1;
open( OUT, "|openssl x509 -text -inform DER -fingerprint" )
|| die "could not pipe to openssl x509";
}
elsif ( /^END/ && $incert )
{
close( OUT );
$incert = 0;
print "\n\n";
}
elsif ($incert)
{
my @bs = split( /\\/ );
foreach my $b (@bs)
{
chomp $b;
printf( OUT "%c", oct($b) ) unless $b eq '';
}
}
}
#!/bin/bash
# Begin remove-expired-certs.sh
#
# Version 20120211
# Make sure the date is parsed correctly on all systems
function mydate()
{
local y=$( echo $1 | cut -d" " -f4 )
local M=$( echo $1 | cut -d" " -f1 )
local d=$( echo $1 | cut -d" " -f2 )
local m
if [ ${d} -lt 10 ]; then d="0${d}"; fi
case $M in
Jan) m="01";;
Feb) m="02";;
Mar) m="03";;
Apr) m="04";;
May) m="05";;
Jun) m="06";;
Jul) m="07";;
Aug) m="08";;
Sep) m="09";;
Oct) m="10";;
Nov) m="11";;
Dec) m="12";;
esac
certdate="${y}${m}${d}"
}
OPENSSL=/usr/bin/openssl
DIR=./certs
if [ $# -gt 0 ]; then
DIR="$1"
fi
certs=$( find ${DIR} -type f -name "*.pem" -o -name "*.crt" )
today=$( date +%Y%m%d )
for cert in $certs; do
notafter=$( $OPENSSL x509 -enddate -in "${cert}" -noout )
date=$( echo ${notafter} | sed 's/^notAfter=//' )
mydate "$date"
if [ ${certdate} -lt ${today} ]; then
echo "${cert} expired on ${certdate}! Removing..."
rm -f "${cert}"
fi
done
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="default-extension-filetype">Files/default-extension-filetype.ini</file>
<file>Certs/Slicer.crt</file>
</qresource>
</RCC>
......@@ -11,6 +11,7 @@ if(BUILD_TESTING)
qSlicerCoreIOManagerTest1.cxx
qSlicerLoadableModuleFactoryTest1.cxx
qSlicerUtilsTest1.cxx
qSlicerSslTest.cxx
)
if(Slicer_BUILD_EXTENSIONMANAGER_SUPPORT)
list(APPEND KIT_TEST_SRCS
......@@ -28,6 +29,10 @@ if(BUILD_TESTING)
EXTRA_INCLUDE vtkMRMLDebugLeaksMacro.h
)
QT4_GENERATE_MOCS(
qSlicerSslTest.cxx
)
if(Slicer_BUILD_EXTENSIONMANAGER_SUPPORT)
QT4_GENERATE_MOCS(
qSlicerExtensionsManagerModelTest.cxx
......@@ -60,6 +65,7 @@ if(BUILD_TESTING)
simple_test( qSlicerAbstractCoreModuleTest1 )
simple_test( qSlicerLoadableModuleFactoryTest1 )
simple_test( qSlicerUtilsTest1 )
simple_test( qSlicerSslTest )
if(Slicer_BUILD_EXTENSIONMANAGER_SUPPORT)
simple_test( qSlicerExtensionsManagerModelTest )
......
// Qt includes
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QSslSocket>
// CTK includes
#include <ctkTest.h>
// Slicer includes
#include "qSlicerCoreApplication.h"
// ----------------------------------------------------------------------------
class qSlicerSslTester: public QObject
{
Q_OBJECT
typedef qSlicerSslTester Self;
private slots:
void testSupportsSsl();
void testLoadCaCertificates();
void testHttpsConnection();
void testHttpsConnection_data();
};
Q_DECLARE_METATYPE(QNetworkReply::NetworkError)
// ----------------------------------------------------------------------------
void qSlicerSslTester::testSupportsSsl()
{
QVERIFY(QSslSocket::supportsSsl());
}
// ----------------------------------------------------------------------------
void qSlicerSslTester::testLoadCaCertificates()
{
QVERIFY(qSlicerCoreApplication::loadCaCertificates());
}
// ----------------------------------------------------------------------------
void qSlicerSslTester::testHttpsConnection()
{
QFETCH(QString, url);
QFETCH(QNetworkReply::NetworkError, expectedNetworkError);
QFETCH(int, expectedStatusCode);
qSlicerCoreApplication::loadCaCertificates();
QNetworkAccessManager * manager = new QNetworkAccessManager(this);
QNetworkReply * reply = manager->get(QNetworkRequest(QUrl(url)));
QEventLoop eventLoop;
QObject::connect(manager, SIGNAL(finished(QNetworkReply*)),
&eventLoop, SLOT(quit()));
QObject::connect(manager, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)),
&eventLoop, SLOT(quit()));
eventLoop.exec();
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
QCOMPARE(reply->error(), expectedNetworkError);
QCOMPARE(statusCode.toInt(), expectedStatusCode);
}
// ----------------------------------------------------------------------------
void qSlicerSslTester::testHttpsConnection_data()
{
QTest::addColumn<QString>("url");
QTest::addColumn<QNetworkReply::NetworkError>("expectedNetworkError");
QTest::addColumn<int>("expectedStatusCode");
QTest::newRow("invalid-HostNotFoundError-0")
<< "http://i.n.v.a.l.i.d"
<< QNetworkReply::HostNotFoundError << 0;
QTest::newRow("clear-NoError-200")
<< "http://slicer.org"
<< QNetworkReply::NoError << 200;
QTest::newRow("secured-NoError-200")
<< "https://www.eff.org/https-everywhere"
<< QNetworkReply::NoError << 200;
}
// ----------------------------------------------------------------------------
CTK_TEST_MAIN(qSlicerSslTest)
#include "moc_qSlicerSslTest.cxx"
......@@ -29,6 +29,8 @@
#include <QTimer>
#include <QNetworkProxyFactory>
#include <QSettings>
#include <QSslCertificate>
#include <QSslSocket>
#include <QTranslator>
// CTK includes
......@@ -174,6 +176,12 @@ void qSlicerCoreApplicationPrivate::init()
QMessageBox::information(0, "Attach process", msg.arg(QCoreApplication::applicationPid()));
}
if (!QSslSocket::supportsSsl())
{
qDebug() << "[SSL] SSL support disabled - Failed to load SSL library !";
}
qSlicerCoreApplication::loadCaCertificates();
QCoreApplication::setOrganizationDomain(Slicer_ORGANIZATION_DOMAIN);
QCoreApplication::setOrganizationName(Slicer_ORGANIZATION_NAME);
......@@ -1545,3 +1553,14 @@ void qSlicerCoreApplication::loadLanguage()
}
#endif
}
//----------------------------------------------------------------------------
bool qSlicerCoreApplication::loadCaCertificates()
{
if (QSslSocket::supportsSsl() && QSslSocket::defaultCaCertificates().empty())
{
qDebug() << "[SSL] No default CA certificates found - Loading Slicer.crt";
QSslSocket::setDefaultCaCertificates(QSslCertificate::fromPath(":/Certs/Slicer.crt"));
}
return !QSslSocket::defaultCaCertificates().empty();
}
......@@ -295,6 +295,12 @@ public:
static void loadLanguage();
/// If system certificates couldn't be found, load certicates bundled into 'Slicer.crt'.
/// For more details, see Slicer/Base/QTCore/Resources/Certs/README
/// Returns \a False if 'Slicer.crt' also failed to be loaded.
/// \sa QSslSocket::defaultCaCertificates()
static bool loadCaCertificates();
public slots:
/// Restart the application with the arguments passed at startup time
......
......@@ -83,6 +83,8 @@ set(KIT_SRCS
qSlicerViewersToolBar.cxx
qSlicerViewersToolBar.h
qSlicerViewersToolBar_p.h
qSlicerWebWidget.cxx
qSlicerWebWidget.h
qSlicerWidget.cxx
qSlicerWidget.h
)
......@@ -184,6 +186,7 @@ set(KIT_MOC_SRCS
qSlicerSettingsModulesPanel.h
qSlicerViewersToolBar.h
qSlicerViewersToolBar_p.h
qSlicerWebWidget.h
qSlicerWidget.h
)
......@@ -236,11 +239,11 @@ set(KIT_UI_SRCS
Resources/UI/qSlicerSettingsGeneralPanel.ui
Resources/UI/qSlicerSettingsInternationalizationPanel.ui
Resources/UI/qSlicerSettingsModulesPanel.ui
Resources/UI/qSlicerWebWidget.ui
)
if(Slicer_BUILD_EXTENSIONMANAGER_SUPPORT)
list(APPEND KIT_UI_SRCS
Resources/UI/qSlicerExtensionsButtonBox.ui
Resources/UI/qSlicerExtensionsInstallWidget.ui
Resources/UI/qSlicerExtensionsManageWidget.ui
Resources/UI/qSlicerExtensionsManagerDialog.ui
Resources/UI/qSlicerExtensionsManagerWidget.ui
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>qSlicerExtensionsInstallWidget</class>
<widget class="QWidget" name="qSlicerExtensionsInstallWidget">
<class>qSlicerWebWidget</class>
<widget class="QWidget" name="qSlicerWebWidget">
<property name="geometry">
<rect>
<x>0</x>
......
......@@ -21,21 +21,18 @@
#ifndef __qSlicerExtensionsInstallWidget_h
#define __qSlicerExtensionsInstallWidget_h
// Qt includes
#include <QWidget>
// CTK includes
#include <ctkErrorLogModel.h>
// QtGUI includes
#include "qSlicerBaseQTGUIExport.h"
#include "qSlicerWebWidget.h"
class QNetworkReply;
class qSlicerExtensionsInstallWidgetPrivate;
class qSlicerExtensionsManagerModel;
class Q_SLICER_BASE_QTGUI_EXPORT qSlicerExtensionsInstallWidget
: public QWidget
: public qSlicerWebWidget
{
Q_OBJECT
Q_PROPERTY(QString slicerRevision READ slicerRevision WRITE setSlicerRevision)
......@@ -43,7 +40,7 @@ class Q_SLICER_BASE_QTGUI_EXPORT qSlicerExtensionsInstallWidget
Q_PROPERTY(QString slicerArch READ slicerArch WRITE setSlicerArch)
public:
/// Superclass typedef
typedef QWidget Superclass;
typedef qSlicerWebWidget Superclass;
/// Constructor
explicit qSlicerExtensionsInstallWidget(QWidget* parent = 0);
......@@ -77,29 +74,17 @@ public slots:
void onMessageLogged(const QString& text, ctkErrorLogLevel::LogLevels level);
void onDownloadStarted(QNetworkReply* reply);
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onDownloadFinished(QNetworkReply* reply);
protected slots:
void initJavascript();
void onLoadStarted();
void onLoadFinished(bool ok);
void onLinkClicked(const QUrl& url);
virtual void initJavascript();
virtual void onLoadFinished(bool ok);
virtual void onLinkClicked(const QUrl& url);
protected:
QScopedPointer<qSlicerExtensionsInstallWidgetPrivate> d_ptr;
/// Event filter used to capture WebView Show and Hide events in order to both set
/// "document.webkitHidden" property and trigger the associated event.
bool eventFilter(QObject *obj, QEvent *event);
private:
Q_DECLARE_PRIVATE(qSlicerExtensionsInstallWidget);
Q_DISABLE_COPY(qSlicerExtensionsInstallWidget);
};
#endif
/*==============================================================================
Program: 3D Slicer
Copyright (c) Kitware Inc.
See COPYRIGHT.txt
or http://www.slicer.org/copyright/copyright.txt for details.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.
and was partially funded by NIH grant 3P41RR013218-12S1
==============================================================================*/
// Qt includes
#include <QDebug>
#include <QDesktopServices>
#include <QNetworkCookieJar>
#include <QNetworkReply>
#include <QSettings>
#include <QTime>
#include <QUrl>
#include <QWebFrame>
// QtCore includes
#include <qSlicerPersistentCookieJar.h>
// QtGUI includes
#include "qSlicerWebWidget.h"
#include "ui_qSlicerWebWidget.h"
//-----------------------------------------------------------------------------
class qSlicerWebWidgetPrivate: public Ui_qSlicerWebWidget
{
Q_DECLARE_PUBLIC(qSlicerWebWidget);
protected:
qSlicerWebWidget* const q_ptr;
public:
qSlicerWebWidgetPrivate(qSlicerWebWidget& object);
void init();
/// Convenient function to return the mainframe
QWebFrame* mainFrame();
/// Convenient method to set "document.webkitHidden" property
void setDocumentWebkitHidden(bool value);
QTime DownloadTime;
};
// --------------------------------------------------------------------------
qSlicerWebWidgetPrivate::qSlicerWebWidgetPrivate(qSlicerWebWidget& object)
:q_ptr(&object)
{
}
// --------------------------------------------------------------------------
void qSlicerWebWidgetPrivate::init()
{
Q_Q(qSlicerWebWidget);
this->setupUi(q);
this->WebView->installEventFilter(q);
QNetworkAccessManager * networkAccessManager = this->WebView->page()->networkAccessManager();;
Q_ASSERT(networkAccessManager);
networkAccessManager->setCookieJar(new qSlicerPersistentCookieJar());
QObject::connect(this->WebView, SIGNAL(loadStarted()),
q, SLOT(onLoadStarted()));
QObject::connect(this->WebView, SIGNAL(loadFinished(bool)),
q, SLOT(onLoadFinished(bool)));
QObject::connect(this->WebView, SIGNAL(loadProgress(int)),
this->ProgressBar, SLOT(setValue(int)));
QObject::connect(this->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()),
q, SLOT(initJavascript()));
this->WebView->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
this->ProgressBar->setVisible(false);
this->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOn);
this->WebView->page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks);
QObject::connect(this->WebView->page(), SIGNAL(linkClicked(QUrl)),
q, SLOT(onLinkClicked(QUrl)));
QObject::connect(networkAccessManager,
SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )),
q, SLOT(handleSslErrors(QNetworkReply*, const QList<QSslError> & )));
}
// --------------------------------------------------------------------------
QWebFrame* qSlicerWebWidgetPrivate::mainFrame()
{
return this->WebView->page()->mainFrame();
}
// --------------------------------------------------------------------------
void qSlicerWebWidgetPrivate::setDocumentWebkitHidden(bool value)
{
Q_Q(qSlicerWebWidget);
q->evalJS(QString("document.webkitHidden = %1").arg(value ? "true" : "false"));
}
// --------------------------------------------------------------------------
qSlicerWebWidget::qSlicerWebWidget(QWidget* _parent)
: Superclass(_parent)
, d_ptr(new qSlicerWebWidgetPrivate(*this))
{
Q_D(qSlicerWebWidget);
d->init();
}
// --------------------------------------------------------------------------
qSlicerWebWidget::~qSlicerWebWidget()
{
}
// --------------------------------------------------------------------------
QWebView * qSlicerWebWidget::webView()
{
Q_D(qSlicerWebWidget);
return d->WebView;
}
//-----------------------------------------------------------------------------
QString qSlicerWebWidget::evalJS(const QString &js)
{
Q_D(qSlicerWebWidget);
return d->mainFrame()->evaluateJavaScript(js).toString();
}
// --------------------------------------------------------------------------
void qSlicerWebWidget::onDownloadStarted(QNetworkReply* reply)
{
Q_D(qSlicerWebWidget);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
SLOT(onDownloadProgress(qint64,qint64)));
d->DownloadTime.start();
d->ProgressBar->setVisible(true);
}
// --------------------------------------------------------------------------
void qSlicerWebWidget::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
Q_D(qSlicerWebWidget);
// Calculate the download speed
double speed = bytesReceived * 1000.0 / d->DownloadTime.elapsed();
QString unit;
if (speed < 1024)
{
unit = "bytes/sec";