libcurl substitute for Qt network.

Reasoning: on Qt <4.7, custom request are not available.
They are required however to implement WebDAV. libcurl
provides a substitute for QNetwork. This is important
for deployment on CentOS6 for example.
This commit is contained in:
Matthias Koefferlein 2018-04-30 00:36:34 +02:00
parent 4c4b079286
commit 080623ad15
13 changed files with 1962 additions and 427 deletions

View File

@ -43,6 +43,11 @@ equals(HAVE_PYTHON, "1") {
DEFINES += HAVE_PYTHON
}
equals(HAVE_CURL, "1") {
DEFINES += HAVE_CURL
LIBS += -lcurl
}
equals(HAVE_RUBY, "1") {
DEFINES += \
HAVE_RUBY \

View File

@ -57,7 +57,8 @@ HEADERS = \
layFontController.h \
layNativePlugin.h \
laySystemPaths.h \
layMacroEditorSetupPage.h
layMacroEditorSetupPage.h \
layPasswordDialog.h
FORMS = \
ClipDialog.ui \
@ -105,7 +106,8 @@ FORMS = \
SaltGrainTemplateSelectionDialog.ui \
SaltManagerInstallConfirmationDialog.ui \
CustomizeMenuConfigPage.ui \
MacroEditorSetupPage.ui
MacroEditorSetupPage.ui \
PasswordDialog.ui
SOURCES = \
gsiDeclLayApplication.cc \
@ -160,7 +162,8 @@ SOURCES = \
layFontController.cc \
layNativePlugin.cc \
laySystemPaths.cc \
layMacroEditorSetupPage.cc
layMacroEditorSetupPage.cc \
layPasswordDialog.cc
RESOURCES = layBuildInMacros.qrc \
layHelpResources.qrc \

View File

@ -38,6 +38,7 @@
#include "layTechnologyController.h"
#include "laySaltController.h"
#include "laySystemPaths.h"
#include "layPasswordDialog.h"
#include "lymMacro.h"
#include "gtf.h"
#include "gsiDecl.h"
@ -56,6 +57,7 @@
#include "tlExpression.h"
#include "tlExceptions.h"
#include "tlInternational.h"
#include "tlHttpStream.h"
#include "tlArch.h"
#include <QIcon>
@ -1520,6 +1522,10 @@ GuiApplication::setup ()
mp_mw = new lay::MainWindow (this, "main_window");
QObject::connect (mp_mw, SIGNAL (closed ()), this, SLOT (quit ()));
// create a password dialog for use with the HTTP streams
lay::PasswordDialog *pw_dialog = new lay::PasswordDialog (mp_mw);
tl::InputHttpStream::set_credential_provider (pw_dialog);
}
void

View File

@ -0,0 +1,62 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2018 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "layPasswordDialog.h"
namespace lay
{
PasswordDialog::PasswordDialog (QWidget *parent)
: QDialog (parent)
{
setupUi (this);
}
bool
PasswordDialog::user_password (const std::string &url, const std::string &realm, bool proxy, int attempt, std::string &user, std::string &passwd)
{
realm_label->setText (tr ("<b>Realm:</b> ") + tl::to_qstring (realm));
if (proxy) {
where_label->setText (tr ("<b>Proxy:</b> ") + tl::to_qstring (url));
} else {
where_label->setText (tr ("<b>URL:</b> ") + tl::to_qstring (url));
}
if (attempt > 1) {
attempt_label->setText (tr ("Authentication failed - please try again"));
attempt_label->show ();
} else {
attempt_label->hide ();
}
if (QDialog::exec ()) {
passwd = tl::to_string (password_le->text ());
user = tl::to_string (user_le->text ());
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,50 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2018 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_layPasswordDialog
#define HDR_layPasswordDialog
#include "tlHttpStream.h"
#include "ui_PasswordDialog.h"
#include <QDialog>
namespace lay
{
/**
* @brief A password dialog for registration with tl::HttpStream
*/
class PasswordDialog
: public QDialog, public tl::HttpCredentialProvider, private Ui::PasswordDialog
{
public:
PasswordDialog (QWidget *parent);
bool user_password (const std::string &url, const std::string &realm, bool proxy, int attempt, std::string &user, std::string &passwd);
};
}
#endif

View File

@ -8,8 +8,7 @@ DEFINES += MAKE_TL_LIBRARY
LIBS += -lz
FORMS = \
PasswordDialog.ui
FORMS =
SOURCES = \
tlAssert.cc \
@ -43,7 +42,9 @@ SOURCES = \
tlArch.cc \
tlCommandLineParser.cc \
tlUnitTest.cc \
tlInt128Support.cc
tlInt128Support.cc \
tlHttpStreamCurl.cc \
tlHttpStreamQt.cc
HEADERS = \
tlAlgorithm.h \
@ -92,7 +93,9 @@ HEADERS = \
tlArch.h \
tlCommandLineParser.h \
tlUnitTest.h \
tlInt128Support.h
tlInt128Support.h \
tlHttpStreamCurl.h \
tlHttpStreamQt.h
INCLUDEPATH =
DEPENDPATH =

View File

@ -22,287 +22,10 @@
#include "tlHttpStream.h"
#include "tlLog.h"
#include "tlStaticObjects.h"
#include "tlDeferredExecution.h"
#include "ui_PasswordDialog.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QAuthenticator>
#include <QCoreApplication>
#include <QDateTime>
#include <QFileInfo>
#include <QDialog>
namespace tl
{
// ---------------------------------------------------------------
// PasswordDialog definition and implementation
class PasswordDialog
: public QDialog, private Ui::PasswordDialog
{
public:
PasswordDialog (QWidget *parent)
: QDialog (parent)
{
setupUi (this);
}
bool exec_auth (bool proxy, int attempt, const QString &where, QAuthenticator *auth)
{
realm_label->setText (tr ("<b>Realm:</b> ") + auth->realm ());
if (proxy) {
where_label->setText (tr ("<b>Proxy:</b> ") + where);
} else {
where_label->setText (tr ("<b>URL:</b> ") + where);
}
if (attempt > 1) {
attempt_label->setText (tr ("Authentication failed - please try again"));
attempt_label->show ();
} else {
attempt_label->hide ();
}
if (QDialog::exec ()) {
auth->setPassword (password_le->text ());
auth->setUser (user_le->text ());
return true;
} else {
return false;
}
}
};
// ---------------------------------------------------------------
// AuthenticationHandler implementation
AuthenticationHandler::AuthenticationHandler ()
: QObject (0), m_retry (0), m_proxy_retry (0)
{
// .. nothing yet ..
}
void
AuthenticationHandler::reset ()
{
m_retry = 0;
m_proxy_retry = 0;
}
void
AuthenticationHandler::authenticationRequired (QNetworkReply *reply, QAuthenticator *auth)
{
PasswordDialog pw_dialog (0 /*no parent*/);
pw_dialog.exec_auth (false, ++m_retry, reply->url ().toString (), auth);
}
void
AuthenticationHandler::proxyAuthenticationRequired (const QNetworkProxy &proxy, QAuthenticator *auth)
{
PasswordDialog pw_dialog (0 /*no parent*/);
pw_dialog.exec_auth (true, ++m_proxy_retry, proxy.hostName (), auth);
}
// ---------------------------------------------------------------
// InputHttpFile implementation
static QNetworkAccessManager *s_network_manager (0);
static AuthenticationHandler *s_auth_handler (0);
InputHttpStream::InputHttpStream (const std::string &url)
: m_url (url), mp_reply (0), m_request ("GET"), mp_buffer (0)
{
if (! s_network_manager) {
s_network_manager = new QNetworkAccessManager (0);
s_auth_handler = new AuthenticationHandler ();
connect (s_network_manager, SIGNAL (authenticationRequired (QNetworkReply *, QAuthenticator *)), s_auth_handler, SLOT (authenticationRequired (QNetworkReply *, QAuthenticator *)));
connect (s_network_manager, SIGNAL (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)), s_auth_handler, SLOT (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)));
tl::StaticObjects::reg (&s_network_manager);
tl::StaticObjects::reg (&s_auth_handler);
}
connect (s_network_manager, SIGNAL (finished (QNetworkReply *)), this, SLOT (finished (QNetworkReply *)));
}
InputHttpStream::~InputHttpStream ()
{
// .. nothing yet ..
}
void
InputHttpStream::set_request (const char *r)
{
m_request = QByteArray (r);
}
void
InputHttpStream::set_data (const char *data)
{
m_data = QByteArray (data);
}
void
InputHttpStream::set_data (const char *data, size_t n)
{
m_data = QByteArray (data, int (n));
}
void
InputHttpStream::add_header (const std::string &name, const std::string &value)
{
m_headers.insert (std::make_pair (name, value));
}
void
InputHttpStream::finished (QNetworkReply *reply)
{
if (reply != mp_active_reply.get ()) {
return;
}
QVariant redirect_target = reply->attribute (QNetworkRequest::RedirectionTargetAttribute);
if (reply->error () == QNetworkReply::NoError && ! redirect_target.isNull ()) {
m_url = tl::to_string (redirect_target.toString ());
if (tl::verbosity() >= 30) {
tl::info << "HTTP redirect to: " << m_url;
}
issue_request (QUrl (redirect_target.toString ()));
} else {
mp_reply = reply;
m_ready ();
}
}
void
InputHttpStream::issue_request (const QUrl &url)
{
delete mp_buffer;
mp_buffer = 0;
// reset the retry counters -> this way we can detect authentication failures
s_auth_handler->reset ();
QNetworkRequest request (url);
if (tl::verbosity() >= 30) {
tl::info << "HTTP request URL: " << url.toString ().toUtf8 ().constData ();
}
for (std::map<std::string, std::string>::const_iterator h = m_headers.begin (); h != m_headers.end (); ++h) {
if (tl::verbosity() >= 40) {
tl::info << "HTTP request header: " << h->first << ": " << h->second;
}
request.setRawHeader (QByteArray (h->first.c_str ()), QByteArray (h->second.c_str ()));
}
#if QT_VERSION < 0x40700
if (m_request == "GET" && m_data.isEmpty ()) {
mp_active_reply.reset (s_network_manager->get (request));
} else {
throw tl::Exception (tl::to_string (QObject::tr ("Custom HTTP requests are not supported in this build (verb is %1)").arg (QString::fromUtf8 (m_request))));
}
#else
if (m_data.isEmpty ()) {
mp_active_reply.reset (s_network_manager->sendCustomRequest (request, m_request));
} else {
if (tl::verbosity() >= 40) {
tl::info << "HTTP request data: " << m_data.constData ();
}
mp_buffer = new QBuffer (&m_data);
mp_active_reply.reset (s_network_manager->sendCustomRequest (request, m_request, mp_buffer));
}
#endif
}
void
InputHttpStream::send ()
{
if (mp_reply == 0) {
issue_request (QUrl (tl::to_qstring (m_url)));
}
}
size_t
InputHttpStream::read (char *b, size_t n)
{
// Prevents deferred methods to be executed during the processEvents below (undesired side effects)
tl::NoDeferredMethods silent;
if (mp_reply == 0) {
issue_request (QUrl (tl::to_qstring (m_url)));
}
// TODO: progress, timeout
while (mp_reply == 0) {
QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents);
}
if (mp_reply->error () != QNetworkReply::NoError) {
// throw an error
std::string em = tl::to_string (mp_reply->attribute (QNetworkRequest::HttpReasonPhraseAttribute).toString ());
if (tl::verbosity() >= 30) {
tl::info << "HTTP response error: " << em;
}
int ec = mp_reply->attribute (QNetworkRequest::HttpStatusCodeAttribute).toInt ();
if (ec == 0) {
switch (mp_reply->error ()) {
case QNetworkReply::ConnectionRefusedError:
em = tl::to_string (QObject::tr ("Connection refused"));
break;
case QNetworkReply::RemoteHostClosedError:
em = tl::to_string (QObject::tr ("Remote host closed connection"));
break;
case QNetworkReply::HostNotFoundError:
em = tl::to_string (QObject::tr ("Host not found"));
break;
case QNetworkReply::TimeoutError:
em = tl::to_string (QObject::tr ("Timeout"));
break;
case QNetworkReply::ContentAccessDenied:
em = tl::to_string (QObject::tr ("Access denied"));
break;
case QNetworkReply::ContentNotFoundError:
em = tl::to_string (QObject::tr ("Content not found"));
break;
default:
em = tl::to_string (QObject::tr ("Network API error"));
}
ec = int (mp_reply->error ());
}
throw HttpErrorException (em, ec, m_url);
}
QByteArray data = mp_reply->read (n);
memcpy (b, data.constData (), data.size ());
if (tl::verbosity() >= 40) {
tl::info << "HTTP reponse data read: " << data.constData ();
}
return data.size ();
}
void
InputHttpStream::reset ()
{
throw tl::Exception (tl::to_string (QObject::tr ("'reset' is not supported on HTTP input streams")));
}
std::string
InputHttpStream::filename () const
{
return tl::to_string (QFileInfo (QUrl (tl::to_qstring (m_url)).path ()).fileName ());
}
}

View File

@ -20,26 +20,36 @@
*/
#ifndef HDR_tlHttpStream
#define HDR_tlHttpStream
#include "tlStream.h"
#include "tlEvents.h"
#include "tlHttpStreamCurl.h"
#include "tlHttpStreamQt.h"
#include <QObject>
#include <QBuffer>
#include <QByteArray>
#include <memory>
class QNetworkAccessManager;
class QNetworkReply;
class QNetworkProxy;
class QAuthenticator;
#include "tlObject.h"
namespace tl
{
/**
* @brief A callback interface to provide the authentication data
*/
class TL_PUBLIC HttpCredentialProvider
: public tl::Object
{
public:
HttpCredentialProvider () { }
virtual ~HttpCredentialProvider () { }
/**
* @brief Gets the user name and password for the given URL and authentication realm
*/
virtual bool user_password (const std::string &url, const std::string &realm, bool proxy, int attempt, std::string &user, std::string &passwd) = 0;
};
/**
* @brief An exception class for HTTP errors
*/
class TL_PUBLIC HttpErrorException
: public tl::Exception
{
@ -49,137 +59,6 @@ public:
{ }
};
class AuthenticationHandler
: public QObject
{
Q_OBJECT
public:
AuthenticationHandler ();
public slots:
void authenticationRequired (QNetworkReply *, QAuthenticator *);
void proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *);
void reset ();
private:
int m_retry, m_proxy_retry;
};
/**
* @brief A http input delegate for tl::InputStream
*
* Implements the reader from a server using the HTTP protocol
*/
class TL_PUBLIC InputHttpStream
: public QObject, public InputStreamBase
{
Q_OBJECT
public:
/**
* @brief Open a stream with the given URL
*/
InputHttpStream (const std::string &url);
/**
* @brief Close the file
*
* The destructor will automatically close the connection.
*/
virtual ~InputHttpStream ();
/**
* @brief Sends the request for data
* To ensure prompt delivery of data, this method can be used prior to
* "read" to trigger the download from the given URL.
* This method will return immediately. When the reply is available,
* the "ready" event will be triggered. "read" can then be used to
* read the data or - in case of an error - throw an exception.
* If "send" is not used before "read", "read" will block until data
* is available.
* If a request has already been sent, this method will do nothing.
*/
void send ();
/**
* @brief Sets the request verb
* The default verb is "GET"
*/
void set_request (const char *r);
/**
* @brief Sets data to be sent with the request
* If data is given, it is sent along with the request.
* This version takes a null-terminated string.
*/
void set_data (const char *data);
/**
* @brief Sets data to be sent with the request
* If data is given, it is sent along with the request.
* This version takes a data plus length.
*/
void set_data (const char *data, size_t n);
/**
* @brief Sets a header field
*/
void add_header (const std::string &name, const std::string &value);
/**
* @brief Read from the stream
* Implements the basic read method.
*/
virtual size_t read (char *b, size_t n);
/**
* @brief Gets the "ready" event
* Connect to this event for the asynchroneous interface.
*/
tl::Event &ready ()
{
return m_ready;
}
/**
* @brief Gets a value indicating whether data is available
*/
bool data_available ()
{
return mp_reply != 0;
}
virtual void reset ();
virtual std::string source () const
{
return m_url;
}
virtual std::string absolute_path () const
{
return m_url;
}
virtual std::string filename () const;
private slots:
void finished (QNetworkReply *);
private:
std::string m_url;
QNetworkReply *mp_reply;
std::auto_ptr<QNetworkReply> mp_active_reply;
QByteArray m_request;
QByteArray m_data;
QBuffer *mp_buffer;
std::map<std::string, std::string> m_headers;
tl::Event m_ready;
void issue_request (const QUrl &url);
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,144 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2018 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_tlHttpStreamCurl
#define HDR_tlHttpStreamCurl
#if defined(HAVE_CURL)
#include "tlStream.h"
#include "tlEvents.h"
#include "tlObject.h"
#include <memory>
namespace tl
{
class CurlConnection;
class HttpCredentialProvider;
/**
* @brief A http input delegate for tl::InputStream
*
* Implements the reader from a server using the HTTP protocol
*/
class TL_PUBLIC InputHttpStream
// NOTE: QObject is required because we use "deleteLater"
: public QObject, public tl::Object, public InputStreamBase
{
public:
/**
* @brief Open a stream with the given URL
*/
InputHttpStream (const std::string &url);
/**
* @brief Close the file
*
* The destructor will automatically close the connection.
*/
virtual ~InputHttpStream ();
/**
* @brief Sets the credential provider
*/
static void set_credential_provider (HttpCredentialProvider *cp);
/**
* @brief Sends the request for data
* To ensure prompt delivery of data, this method can be used prior to
* "read" to trigger the download from the given URL.
* This method will return immediately. When the reply is available,
* the "ready" event will be triggered. "read" can then be used to
* read the data or - in case of an error - throw an exception.
* If "send" is not used before "read", "read" will block until data
* is available.
* If a request has already been sent, this method will do nothing.
*/
void send ();
/**
* @brief Sets the request verb
* The default verb is "GET"
*/
void set_request (const char *r);
/**
* @brief Sets data to be sent with the request
* If data is given, it is sent along with the request.
* This version takes a null-terminated string.
*/
void set_data (const char *data);
/**
* @brief Sets data to be sent with the request
* If data is given, it is sent along with the request.
* This version takes a data plus length.
*/
void set_data (const char *data, size_t n);
/**
* @brief Sets a header field
*/
void add_header (const std::string &name, const std::string &value);
/**
* @brief Read from the stream
* Implements the basic read method.
*/
virtual size_t read (char *b, size_t n);
/**
* @brief Gets the "ready" event
* Connect to this event for the asynchroneous interface.
*/
tl::Event &ready ()
{
return m_ready_event;
}
/**
* @brief Gets a value indicating whether data is available
*/
bool data_available ();
virtual void reset ();
virtual std::string source () const;
virtual std::string absolute_path () const;
virtual std::string filename () const;
private:
std::auto_ptr<CurlConnection> m_connection;
tl::Event m_ready_event;
bool m_sent;
void finished ();
};
}
#endif
#endif

296
src/tl/tl/tlHttpStreamQt.cc Normal file
View File

@ -0,0 +1,296 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2018 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#if !defined(HAVE_CURL)
#include "tlHttpStream.h"
#include "tlLog.h"
#include "tlStaticObjects.h"
#include "tlDeferredExecution.h"
#include "tlObject.h"
#include "ui_PasswordDialog.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QAuthenticator>
#include <QCoreApplication>
#include <QDateTime>
#include <QFileInfo>
#include <QDialog>
namespace tl
{
tl::weak_ptr<HttpCredentialProvider> sp_credential_provider;
// ---------------------------------------------------------------
// AuthenticationHandler implementation
AuthenticationHandler::AuthenticationHandler ()
: QObject (0), m_retry (0), m_proxy_retry (0)
{
// .. nothing yet ..
}
void
AuthenticationHandler::reset ()
{
m_retry = 0;
m_proxy_retry = 0;
}
void
AuthenticationHandler::authenticationRequired (QNetworkReply *reply, QAuthenticator *auth)
{
if (sp_credential_provider.get ()) {
// TODO: how to cancel?
std::string user, passwd;
if (sp_credential_provider->user_password (tl::to_string (reply->url ().toString ()), tl::to_string (auth->realm ()), true, ++m_retry, user, passwd)) {
auth->setPassword (tl::to_qstring (passwd));
auth->setUser (tl::to_qstring (user));
}
}
}
void
AuthenticationHandler::proxyAuthenticationRequired (const QNetworkProxy &proxy, QAuthenticator *auth)
{
if (sp_credential_provider.get ()) {
// TODO: how to cancel?
std::string user, passwd;
if (sp_credential_provider->user_password (tl::to_string (proxy.hostName ()), tl::to_string (auth->realm ()), true, ++m_proxy_retry, user, passwd)) {
auth->setPassword (tl::to_qstring (passwd));
auth->setUser (tl::to_qstring (user));
}
}
}
// ---------------------------------------------------------------
// InputHttpFileQt implementation
static QNetworkAccessManager *s_network_manager (0);
static AuthenticationHandler *s_auth_handler (0);
InputHttpStream::InputHttpStream (const std::string &url)
: m_url (url), mp_reply (0), m_request ("GET"), mp_buffer (0)
{
if (! s_network_manager) {
s_network_manager = new QNetworkAccessManager (0);
s_auth_handler = new AuthenticationHandler ();
connect (s_network_manager, SIGNAL (authenticationRequired (QNetworkReply *, QAuthenticator *)), s_auth_handler, SLOT (authenticationRequired (QNetworkReply *, QAuthenticator *)));
connect (s_network_manager, SIGNAL (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)), s_auth_handler, SLOT (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)));
tl::StaticObjects::reg (&s_network_manager);
tl::StaticObjects::reg (&s_auth_handler);
}
connect (s_network_manager, SIGNAL (finished (QNetworkReply *)), this, SLOT (finished (QNetworkReply *)));
}
InputHttpStream::~InputHttpStream ()
{
// .. nothing yet ..
}
void
InputHttpStream::set_credential_provider (HttpCredentialProvider *cp)
{
sp_credential_provider.reset (cp);
}
void
InputHttpStream::set_request (const char *r)
{
m_request = QByteArray (r);
}
void
InputHttpStream::set_data (const char *data)
{
m_data = QByteArray (data);
}
void
InputHttpStream::set_data (const char *data, size_t n)
{
m_data = QByteArray (data, int (n));
}
void
InputHttpStream::add_header (const std::string &name, const std::string &value)
{
m_headers.insert (std::make_pair (name, value));
}
void
InputHttpStream::finished (QNetworkReply *reply)
{
if (reply != mp_active_reply.get ()) {
return;
}
QVariant redirect_target = reply->attribute (QNetworkRequest::RedirectionTargetAttribute);
if (reply->error () == QNetworkReply::NoError && ! redirect_target.isNull ()) {
m_url = tl::to_string (redirect_target.toString ());
if (tl::verbosity() >= 30) {
tl::info << "HTTP redirect to: " << m_url;
}
issue_request (QUrl (redirect_target.toString ()));
} else {
mp_reply = reply;
m_ready ();
}
}
void
InputHttpStream::issue_request (const QUrl &url)
{
delete mp_buffer;
mp_buffer = 0;
// reset the retry counters -> this way we can detect authentication failures
s_auth_handler->reset ();
QNetworkRequest request (url);
if (tl::verbosity() >= 30) {
tl::info << "HTTP request URL: " << url.toString ().toUtf8 ().constData ();
}
for (std::map<std::string, std::string>::const_iterator h = m_headers.begin (); h != m_headers.end (); ++h) {
if (tl::verbosity() >= 40) {
tl::info << "HTTP request header: " << h->first << ": " << h->second;
}
request.setRawHeader (QByteArray (h->first.c_str ()), QByteArray (h->second.c_str ()));
}
#if QT_VERSION < 0x40700
if (m_request == "GET" && m_data.isEmpty ()) {
mp_active_reply.reset (s_network_manager->get (request));
} else {
throw tl::Exception (tl::to_string (QObject::tr ("Custom HTTP requests are not supported in this build (verb is %1)").arg (QString::fromUtf8 (m_request))));
}
#else
if (m_data.isEmpty ()) {
mp_active_reply.reset (s_network_manager->sendCustomRequest (request, m_request));
} else {
if (tl::verbosity() >= 40) {
tl::info << "HTTP request data: " << m_data.constData ();
}
mp_buffer = new QBuffer (&m_data);
mp_active_reply.reset (s_network_manager->sendCustomRequest (request, m_request, mp_buffer));
}
#endif
}
void
InputHttpStream::send ()
{
if (mp_reply == 0) {
issue_request (QUrl (tl::to_qstring (m_url)));
}
}
size_t
InputHttpStream::read (char *b, size_t n)
{
// Prevents deferred methods to be executed during the processEvents below (undesired side effects)
tl::NoDeferredMethods silent;
if (mp_reply == 0) {
issue_request (QUrl (tl::to_qstring (m_url)));
}
// TODO: progress, timeout
while (mp_reply == 0) {
QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents);
}
if (mp_reply->error () != QNetworkReply::NoError) {
// throw an error
std::string em = tl::to_string (mp_reply->attribute (QNetworkRequest::HttpReasonPhraseAttribute).toString ());
if (tl::verbosity() >= 30) {
tl::info << "HTTP response error: " << em;
}
int ec = mp_reply->attribute (QNetworkRequest::HttpStatusCodeAttribute).toInt ();
if (ec == 0) {
switch (mp_reply->error ()) {
case QNetworkReply::ConnectionRefusedError:
em = tl::to_string (QObject::tr ("Connection refused"));
break;
case QNetworkReply::RemoteHostClosedError:
em = tl::to_string (QObject::tr ("Remote host closed connection"));
break;
case QNetworkReply::HostNotFoundError:
em = tl::to_string (QObject::tr ("Host not found"));
break;
case QNetworkReply::TimeoutError:
em = tl::to_string (QObject::tr ("Timeout"));
break;
case QNetworkReply::ContentAccessDenied:
em = tl::to_string (QObject::tr ("Access denied"));
break;
case QNetworkReply::ContentNotFoundError:
em = tl::to_string (QObject::tr ("Content not found"));
break;
default:
em = tl::to_string (QObject::tr ("Network API error"));
}
ec = int (mp_reply->error ());
}
throw HttpErrorException (em, ec, m_url);
}
QByteArray data = mp_reply->read (n);
memcpy (b, data.constData (), data.size ());
if (tl::verbosity() >= 40) {
tl::info << "HTTP reponse data read: " << data.constData ();
}
return data.size ();
}
void
InputHttpStream::reset ()
{
throw tl::Exception (tl::to_string (QObject::tr ("'reset' is not supported on HTTP input streams")));
}
std::string
InputHttpStream::filename () const
{
return tl::to_string (QFileInfo (QUrl (tl::to_qstring (m_url)).path ()).fileName ());
}
}
#endif

188
src/tl/tl/tlHttpStreamQt.h Normal file
View File

@ -0,0 +1,188 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2018 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_tlHttpStreamQt
#define HDR_tlHttpStreamQt
#if !defined(HAVE_CURL)
#include "tlStream.h"
#include "tlEvents.h"
#include <QObject>
#include <QBuffer>
#include <QByteArray>
#include <memory>
class QNetworkAccessManager;
class QNetworkReply;
class QNetworkProxy;
class QAuthenticator;
namespace tl
{
class HttpCredentialProvider;
class AuthenticationHandler
: public QObject
{
Q_OBJECT
public:
AuthenticationHandler ();
public slots:
void authenticationRequired (QNetworkReply *, QAuthenticator *);
void proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *);
void reset ();
private:
int m_retry, m_proxy_retry;
};
/**
* @brief A http input delegate for tl::InputStream
*
* Implements the reader from a server using the HTTP protocol
*/
class TL_PUBLIC InputHttpStream
: public QObject, public InputStreamBase
{
Q_OBJECT
public:
/**
* @brief Open a stream with the given URL
*/
InputHttpStream (const std::string &url);
/**
* @brief Close the file
*
* The destructor will automatically close the connection.
*/
virtual ~InputHttpStream ();
/**
* @brief Sets the credential provider
*/
static void set_credential_provider (HttpCredentialProvider *cp);
/**
* @brief Sends the request for data
* To ensure prompt delivery of data, this method can be used prior to
* "read" to trigger the download from the given URL.
* This method will return immediately. When the reply is available,
* the "ready" event will be triggered. "read" can then be used to
* read the data or - in case of an error - throw an exception.
* If "send" is not used before "read", "read" will block until data
* is available.
* If a request has already been sent, this method will do nothing.
*/
void send ();
/**
* @brief Sets the request verb
* The default verb is "GET"
*/
void set_request (const char *r);
/**
* @brief Sets data to be sent with the request
* If data is given, it is sent along with the request.
* This version takes a null-terminated string.
*/
void set_data (const char *data);
/**
* @brief Sets data to be sent with the request
* If data is given, it is sent along with the request.
* This version takes a data plus length.
*/
void set_data (const char *data, size_t n);
/**
* @brief Sets a header field
*/
void add_header (const std::string &name, const std::string &value);
/**
* @brief Read from the stream
* Implements the basic read method.
*/
virtual size_t read (char *b, size_t n);
/**
* @brief Gets the "ready" event
* Connect to this event for the asynchroneous interface.
*/
tl::Event &ready ()
{
return m_ready;
}
/**
* @brief Gets a value indicating whether data is available
*/
bool data_available ()
{
return mp_reply != 0;
}
virtual void reset ();
virtual std::string source () const
{
return m_url;
}
virtual std::string absolute_path () const
{
return m_url;
}
virtual std::string filename () const;
private slots:
void finished (QNetworkReply *);
private:
std::string m_url;
QNetworkReply *mp_reply;
std::auto_ptr<QNetworkReply> mp_active_reply;
QByteArray m_request;
QByteArray m_data;
QBuffer *mp_buffer;
std::map<std::string, std::string> m_headers;
tl::Event m_ready;
void issue_request (const QUrl &url);
};
}
#endif
#endif