1 /****************************************************************************
2 ** Copyright (C) 2010-2020 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com.
3 ** All rights reserved.
4 **
5 ** This file is part of the KD Soap library.
6 **
7 ** Licensees holding valid commercial KD Soap licenses may use this file in
8 ** accordance with the KD Soap Commercial License Agreement provided with
9 ** the Software.
10 **
11 **
12 ** This file may be distributed and/or modified under the terms of the
13 ** GNU Lesser General Public License version 2.1 and version 3 as published by the
14 ** Free Software Foundation and appearing in the file LICENSE.LGPL.txt included.
15 **
16 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
17 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
18 **
19 ** Contact info@kdab.com if any conditions of this licensing are not
20 ** clear to you.
21 **
22 **********************************************************************/
23 #include "KDSoapClientInterface.h"
24 #include "KDSoapClientInterface_p.h"
25 #include "KDSoapNamespaceManager.h"
26 #include "KDSoapMessageWriter_p.h"
27 #ifndef QT_NO_OPENSSL
28 #include "KDSoapSslHandler.h"
29 #include "KDSoapReplySslHandler_p.h"
30 #endif
31 #include "KDSoapPendingCall_p.h"
32 #include <QSslConfiguration>
33 #include <QNetworkRequest>
34 #include <QNetworkReply>
35 #include <QAuthenticator>
36 #include <QDebug>
37 #include <QBuffer>
38 #include <QNetworkProxy>
39 #include <QTimer>
40 
KDSoapClientInterface(const QString & endPoint,const QString & messageNamespace)41 KDSoapClientInterface::KDSoapClientInterface(const QString &endPoint, const QString &messageNamespace)
42     : d(new KDSoapClientInterfacePrivate)
43 {
44     d->m_endPoint = endPoint;
45     d->m_messageNamespace = messageNamespace;
46     d->m_version = KDSoap::SOAP1_1;
47 }
48 
~KDSoapClientInterface()49 KDSoapClientInterface::~KDSoapClientInterface()
50 {
51     d->m_thread.stop();
52     d->m_thread.wait();
53     delete d;
54 }
55 
setSoapVersion(KDSoapClientInterface::SoapVersion version)56 void KDSoapClientInterface::setSoapVersion(KDSoapClientInterface::SoapVersion version)
57 {
58     d->m_version = static_cast<KDSoap::SoapVersion>(version);
59 }
60 
soapVersion() const61 KDSoapClientInterface::SoapVersion KDSoapClientInterface::soapVersion() const
62 {
63     return static_cast<KDSoapClientInterface::SoapVersion>(d->m_version);
64 }
65 
KDSoapClientInterfacePrivate()66 KDSoapClientInterfacePrivate::KDSoapClientInterfacePrivate()
67     : m_accessManager(nullptr),
68       m_authentication(),
69       m_version(KDSoap::SOAP1_1),
70       m_style(KDSoapClientInterface::RPCStyle),
71       m_ignoreSslErrors(false),
72       m_timeout(30 * 60 * 1000) // 30 minutes, as documented
73 {
74 #ifndef QT_NO_OPENSSL
75     m_sslHandler = nullptr;
76 #endif
77 }
78 
~KDSoapClientInterfacePrivate()79 KDSoapClientInterfacePrivate::~KDSoapClientInterfacePrivate()
80 {
81 #ifndef QT_NO_OPENSSL
82     delete m_sslHandler;
83 #endif
84 }
85 
accessManager()86 QNetworkAccessManager *KDSoapClientInterfacePrivate::accessManager()
87 {
88     if (!m_accessManager) {
89         m_accessManager = new QNetworkAccessManager(this);
90         connect(m_accessManager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
91                 this, SLOT(_kd_slotAuthenticationRequired(QNetworkReply*,QAuthenticator*)));
92     }
93     return m_accessManager;
94 }
95 
prepareRequest(const QString & method,const QString & action)96 QNetworkRequest KDSoapClientInterfacePrivate::prepareRequest(const QString &method, const QString &action)
97 {
98     QNetworkRequest request(QUrl(this->m_endPoint));
99 
100     QString soapAction = action;
101 
102     if (soapAction.isNull()) {
103         // The automatic generation of SoapAction done in this block is a mistake going back to KDSoap 1.0.
104         // The spec says "there is no default value for SoapAction" (https://www.w3.org/TR/wsdl#_soap:operation)
105         // but we keep this for compatibility, when nothing was passed as argument (see the webcalls unittest)
106         soapAction = this->m_messageNamespace;
107         if (!soapAction.endsWith(QLatin1Char('/'))) {
108             soapAction += QLatin1Char('/');
109         }
110         soapAction += method;
111     }
112     //qDebug() << "soapAction=" << soapAction;
113 
114     QString soapHeader;
115     if (m_version == KDSoap::SOAP1_1) {
116         soapHeader += QString::fromLatin1("text/xml;charset=utf-8");
117         request.setRawHeader("SoapAction", '\"' + soapAction.toUtf8() + '\"');
118     } else if (m_version == KDSoap::SOAP1_2) {
119         soapHeader += QString::fromLatin1("application/soap+xml;charset=utf-8;action=") + soapAction;
120     }
121 
122     request.setHeader(QNetworkRequest::ContentTypeHeader, soapHeader.toUtf8());
123 
124     // FIXME need to find out which version of Qt this is no longer necessary
125     // without that the server might respond with gzip compressed data and
126     // Qt 4.6.2 fails to decode that properly
127     //
128     // happens with retrieval calls in against SugarCRM 5.5.1 running on Apache 2.2.15
129     // when the response seems to reach a certain size threshold
130     request.setRawHeader("Accept-Encoding", "compress");
131 
132     for (QMap<QByteArray, QByteArray>::const_iterator it = m_httpHeaders.constBegin(); it != m_httpHeaders.constEnd(); ++it) {
133         request.setRawHeader(it.key(), it.value());
134     }
135 
136 #ifndef QT_NO_OPENSSL
137     if (!m_sslConfiguration.isNull()) {
138         request.setSslConfiguration(m_sslConfiguration);
139     }
140 #endif
141 
142     return request;
143 }
144 
prepareRequestBuffer(const QString & method,const KDSoapMessage & message,const KDSoapHeaders & headers)145 QBuffer *KDSoapClientInterfacePrivate::prepareRequestBuffer(const QString &method, const KDSoapMessage &message, const KDSoapHeaders &headers)
146 {
147     KDSoapMessageWriter msgWriter;
148     msgWriter.setMessageNamespace(m_messageNamespace);
149     msgWriter.setVersion(m_version);
150     const QByteArray data = msgWriter.messageToXml(message, (m_style == KDSoapClientInterface::RPCStyle) ? method : QString(), headers, m_persistentHeaders, m_authentication);
151     QBuffer *buffer = new QBuffer;
152     buffer->setData(data);
153     buffer->open(QIODevice::ReadOnly);
154     return buffer;
155 }
156 
asyncCall(const QString & method,const KDSoapMessage & message,const QString & soapAction,const KDSoapHeaders & headers)157 KDSoapPendingCall KDSoapClientInterface::asyncCall(const QString &method, const KDSoapMessage &message, const QString &soapAction, const KDSoapHeaders &headers)
158 {
159     QBuffer *buffer = d->prepareRequestBuffer(method, message, headers);
160     QNetworkRequest request = d->prepareRequest(method, soapAction);
161     QNetworkReply *reply = d->accessManager()->post(request, buffer);
162     d->setupReply(reply);
163     maybeDebugRequest(buffer->data(), reply->request(), reply);
164     KDSoapPendingCall call(reply, buffer);
165     call.d->soapVersion = d->m_version;
166     return call;
167 }
168 
call(const QString & method,const KDSoapMessage & message,const QString & soapAction,const KDSoapHeaders & headers)169 KDSoapMessage KDSoapClientInterface::call(const QString &method, const KDSoapMessage &message, const QString &soapAction, const KDSoapHeaders &headers)
170 {
171     d->accessManager()->cookieJar(); // create it in the right thread, the secondary thread will use it
172     // Problem is: I don't want a nested event loop here. Too dangerous for GUI programs.
173     // I wanted a socket->waitFor... but we don't have access to the actual socket in QNetworkAccess.
174     // So the only option that remains is a thread and acquiring a semaphore...
175     KDSoapThreadTaskData *task = new KDSoapThreadTaskData(this, method, message, soapAction, headers);
176     task->m_authentication = d->m_authentication;
177     d->m_thread.enqueue(task);
178     if (!d->m_thread.isRunning()) {
179         d->m_thread.start();
180     }
181     task->waitForCompletion();
182     KDSoapMessage ret = task->response();
183     d->m_lastResponseHeaders = task->responseHeaders();
184     delete task;
185     return ret;
186 }
187 
callNoReply(const QString & method,const KDSoapMessage & message,const QString & soapAction,const KDSoapHeaders & headers)188 void KDSoapClientInterface::callNoReply(const QString &method, const KDSoapMessage &message, const QString &soapAction, const KDSoapHeaders &headers)
189 {
190     QBuffer *buffer = d->prepareRequestBuffer(method, message, headers);
191     QNetworkRequest request = d->prepareRequest(method, soapAction);
192     QNetworkReply *reply = d->accessManager()->post(request, buffer);
193     d->setupReply(reply);
194     maybeDebugRequest(buffer->data(), reply->request(), reply);
195     QObject::connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater()));
196     QObject::connect(reply, SIGNAL(finished()), buffer, SLOT(deleteLater()));
197 }
198 
_kd_slotAuthenticationRequired(QNetworkReply * reply,QAuthenticator * authenticator)199 void KDSoapClientInterfacePrivate::_kd_slotAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
200 {
201     m_authentication.handleAuthenticationRequired(reply, authenticator);
202 }
203 
setAuthentication(const KDSoapAuthentication & authentication)204 void KDSoapClientInterface::setAuthentication(const KDSoapAuthentication &authentication)
205 {
206     d->m_authentication = authentication;
207 }
208 
endPoint() const209 QString KDSoapClientInterface::endPoint() const
210 {
211     return d->m_endPoint;
212 }
213 
setEndPoint(const QString & endPoint)214 void KDSoapClientInterface::setEndPoint(const QString &endPoint)
215 {
216     d->m_endPoint = endPoint;
217 }
218 
setHeader(const QString & name,const KDSoapMessage & header)219 void KDSoapClientInterface::setHeader(const QString &name, const KDSoapMessage &header)
220 {
221     d->m_persistentHeaders[name] = header;
222     d->m_persistentHeaders[name].setQualified(true);
223 }
224 
ignoreSslErrors()225 void KDSoapClientInterface::ignoreSslErrors()
226 {
227     d->m_ignoreSslErrors = true;
228 }
229 
230 #ifndef QT_NO_OPENSSL
ignoreSslErrors(const QList<QSslError> & errors)231 void KDSoapClientInterface::ignoreSslErrors(const QList<QSslError> &errors)
232 {
233     d->m_ignoreErrorsList = errors;
234 }
235 #endif
236 
237 // Workaround for lack of connect-to-lambdas in Qt4
238 // The pure Qt5 code could read like
239 /*
240     QTimer *timeoutTimer = new QTimer(reply);
241     timeoutTimer->setSingleShot(true);
242     connect(timeoutTimer, &QTimer::timeout, reply, [reply]() { contents_of_the_slot });
243 */
244 class TimeoutHandler : public QTimer // this way a single QObject is needed
245 {
246     Q_OBJECT
247 public:
TimeoutHandler(QNetworkReply * reply)248     TimeoutHandler(QNetworkReply *reply)
249         : QTimer(reply)
250     {
251         setSingleShot(true);
252     }
253 public Q_SLOTS:
replyTimeout()254     void replyTimeout()
255     {
256         QNetworkReply *reply = qobject_cast<QNetworkReply *>(parent());
257         Q_ASSERT(reply);
258 
259         // contents_of_the_slot:
260         reply->setProperty("kdsoap_reply_timed_out", true); // see KDSoapPendingCall.cpp
261         reply->abort();
262     }
263 };
264 
setupReply(QNetworkReply * reply)265 void KDSoapClientInterfacePrivate::setupReply(QNetworkReply *reply)
266 {
267     if (m_ignoreSslErrors) {
268         QObject::connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors()));
269     } else {
270 #ifndef QT_NO_OPENSSL
271         reply->ignoreSslErrors(m_ignoreErrorsList);
272         if (m_sslHandler) {
273             // create a child object of the reply, which will forward to m_sslHandler.
274             // this is a workaround for the lack of the reply pointer in the signal,
275             // and sender() doesn't work for sync calls (from another thread) (SOAP-79/issue29)
276             new KDSoapReplySslHandler(reply, m_sslHandler);
277         }
278 #endif
279     }
280     if (m_timeout >= 0) {
281         TimeoutHandler *timeoutHandler = new TimeoutHandler(reply);
282         connect(timeoutHandler, SIGNAL(timeout()), timeoutHandler, SLOT(replyTimeout()));
283         timeoutHandler->start(m_timeout);
284     }
285 }
286 
lastResponseHeaders() const287 KDSoapHeaders KDSoapClientInterface::lastResponseHeaders() const
288 {
289     return d->m_lastResponseHeaders;
290 }
291 
setStyle(KDSoapClientInterface::Style style)292 void KDSoapClientInterface::setStyle(KDSoapClientInterface::Style style)
293 {
294     d->m_style = style;
295 }
296 
style() const297 KDSoapClientInterface::Style KDSoapClientInterface::style() const
298 {
299     return d->m_style;
300 }
301 
cookieJar() const302 QNetworkCookieJar *KDSoapClientInterface::cookieJar() const
303 {
304     return d->accessManager()->cookieJar();
305 }
306 
setCookieJar(QNetworkCookieJar * jar)307 void KDSoapClientInterface::setCookieJar(QNetworkCookieJar *jar)
308 {
309     QObject *oldParent = jar->parent();
310     d->accessManager()->setCookieJar(jar);
311     jar->setParent(oldParent); // see comment in QNAM::setCookieJar...
312 }
313 
setRawHTTPHeaders(const QMap<QByteArray,QByteArray> & headers)314 void KDSoapClientInterface::setRawHTTPHeaders(const QMap<QByteArray, QByteArray> &headers)
315 {
316     d->m_httpHeaders = headers;
317 }
318 
proxy() const319 QNetworkProxy KDSoapClientInterface::proxy() const
320 {
321     return d->accessManager()->proxy();
322 }
323 
setProxy(const QNetworkProxy & proxy)324 void KDSoapClientInterface::setProxy(const QNetworkProxy &proxy)
325 {
326     d->accessManager()->setProxy(proxy);
327 }
328 
timeout() const329 int KDSoapClientInterface::timeout() const
330 {
331     return d->m_timeout;
332 }
333 
setTimeout(int msecs)334 void KDSoapClientInterface::setTimeout(int msecs)
335 {
336     d->m_timeout = msecs;
337 }
338 
339 #ifndef QT_NO_OPENSSL
sslConfiguration() const340 QSslConfiguration KDSoapClientInterface::sslConfiguration() const
341 {
342     return d->m_sslConfiguration;
343 }
344 
setSslConfiguration(const QSslConfiguration & config)345 void KDSoapClientInterface::setSslConfiguration(const QSslConfiguration &config)
346 {
347     d->m_sslConfiguration = config;
348 }
349 
sslHandler() const350 KDSoapSslHandler *KDSoapClientInterface::sslHandler() const
351 {
352     if (!d->m_sslHandler) {
353         d->m_sslHandler = new KDSoapSslHandler;
354     }
355     return d->m_sslHandler;
356 }
357 #endif
358 
359 #include "moc_KDSoapClientInterface_p.cpp"
360 #include "KDSoapClientInterface.moc"
361