MythTV  master
soapclient.cpp
Go to the documentation of this file.
1 // Program Name: soapclient.cpp
3 // Created : Mar. 19, 2007
4 //
5 // Purpose : SOAP client base class
6 //
7 // Copyright (c) 2007 David Blain <dblain@mythtv.org>
8 //
9 // Licensed under the GPL v2 or later, see LICENSE for details
10 //
12 
13 #include <QBuffer>
14 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
15 #include <QStringConverter>
16 #endif
17 
18 #include "soapclient.h"
19 
22 
23 #include "httprequest.h"
24 #include "upnp.h"
25 
26 #define LOC QString("SOAPClient: ")
27 
35  QString sNamespace,
36  QString sControlPath) :
37  m_url(std::move(url)), m_sNamespace(std::move(sNamespace)),
38  m_sControlPath(std::move(sControlPath))
39 {
40 }
41 
42 
47 bool SOAPClient::Init(const QUrl &url,
48  const QString &sNamespace,
49  const QString &sControlPath)
50 {
51  bool ok = true;
52  if (sNamespace.isEmpty())
53  {
54  ok = false;
55  LOG(VB_GENERAL, LOG_ERR, LOC + "Init() given blank namespace");
56  }
57 
58  QUrl test(url);
59  test.setPath(sControlPath);
60  if (!test.isValid())
61  {
62  ok = false;
63  LOG(VB_GENERAL, LOG_ERR, LOC +
64  QString("Init() given invalid control URL %1")
65  .arg(test.toString()));
66  }
67 
68  if (ok)
69  {
70  m_url = url;
71  m_sNamespace = sNamespace;
72  m_sControlPath = sControlPath;
73  }
74  else
75  {
76  m_url = QUrl();
77  m_sNamespace.clear();
78  m_sControlPath.clear();
79  }
80 
81  return ok;
82 }
83 
86  const QString &sName, const QDomNode &baseNode) const
87 {
88 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
89  QStringList parts = sName.split('/', QString::SkipEmptyParts);
90 #else
91  QStringList parts = sName.split('/', Qt::SkipEmptyParts);
92 #endif
93  return FindNodeInternal(parts, baseNode);
94 }
95 
98  QStringList &sParts, const QDomNode &curNode) const
99 {
100  if (sParts.empty())
101  return curNode;
102 
103  QString sName = sParts.front();
104  sParts.pop_front();
105 
106  QDomNode child = curNode.namedItem(sName);
107 
108  if (child.isNull() )
109  sParts.clear();
110 
111  return FindNodeInternal(sParts, child);
112 }
113 
117  const QDomNode &node, const QString &sName, int nDefault) const
118 {
119  QString sValue = GetNodeValue(node, sName, QString::number(nDefault));
120  return sValue.toInt();
121 }
122 
126  const QDomNode &node, const QString &sName, bool bDefault) const
127 {
128  QString sDefault = (bDefault) ? "true" : "false";
129  QString sValue = GetNodeValue(node, sName, sDefault);
130  if (sValue.isEmpty())
131  return bDefault;
132 
133  char ret = sValue[0].toLatin1();
134  switch (ret)
135  {
136  case 't': case 'T': case 'y': case 'Y': case '1':
137  return true;
138  case 'f': case 'F': case 'n': case 'N': case '0':
139  return false;
140  default:
141  return bDefault;
142  }
143 }
144 
148  const QDomNode &node, const QString &sName, const QString &sDefault) const
149 {
150  if (node.isNull())
151  return sDefault;
152 
153  QString sValue = "";
154  QDomNode valNode = FindNode(sName, node);
155 
156  if (!valNode.isNull())
157  {
158  // -=>TODO: Assumes first child is Text Node.
159 
160  QDomText oText = valNode.firstChild().toText();
161 
162  if (!oText.isNull())
163  sValue = oText.nodeValue();
164 
165  return QUrl::fromPercentEncoding(sValue.toUtf8());
166  }
167 
168  return sDefault;
169 }
170 
187 QDomDocument SOAPClient::SendSOAPRequest(const QString &sMethod,
188  QStringMap &list,
189  int &nErrCode,
190  QString &sErrDesc)
191 {
192  QUrl url(m_url);
193 
194  url.setPath(m_sControlPath);
195 
196  nErrCode = UPnPResult_Success;
197  sErrDesc = "";
198 
199  QDomDocument xmlResult;
200  if (m_sNamespace.isEmpty())
201  {
203  sErrDesc = "No namespace given";
204  return xmlResult;
205  }
206 
207  // --------------------------------------------------------------
208  // Add appropriate headers
209  // --------------------------------------------------------------
210  QHash<QByteArray, QByteArray> headers;
211 
212  headers.insert("Content-Type", "text/xml; charset=\"utf-8\"");
213  QString soapHeader = QString("\"%1#%2\"").arg(m_sNamespace, sMethod);
214  headers.insert("SOAPACTION", soapHeader.toUtf8());
215  headers.insert("User-Agent", "Mozilla/9.876 (X11; U; Linux 2.2.12-20 i686, en) "
216  "Gecko/25250101 Netscape/5.432b1");
217  // --------------------------------------------------------------
218  // Build request payload
219  // --------------------------------------------------------------
220 
221  QByteArray aBuffer;
222  QTextStream os( &aBuffer );
223 
224 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
225  os.setCodec("UTF-8");
226 #else
227  os.setEncoding(QStringConverter::Utf8);
228 #endif
229 
230  os << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
231  os << "<s:Envelope "
232  " s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\""
233  " xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n";
234  os << " <s:Body>\r\n";
235  os << " <u:" << sMethod << " xmlns:u=\"" << m_sNamespace << "\">\r\n";
236 
237  // --------------------------------------------------------------
238  // Add parameters from list
239  // --------------------------------------------------------------
240 
241  for (QStringMap::iterator it = list.begin(); it != list.end(); ++it)
242  {
243  os << " <" << it.key() << ">";
244  os << HTTPRequest::Encode( *it );
245  os << "</" << it.key() << ">\r\n";
246  }
247 
248  os << " </u:" << sMethod << ">\r\n";
249  os << " </s:Body>\r\n";
250  os << "</s:Envelope>\r\n";
251 
252  os.flush();
253 
254  // --------------------------------------------------------------
255  // Perform Request
256  // --------------------------------------------------------------
257 
258  LOG(VB_UPNP, LOG_DEBUG,
259  QString("SOAPClient(%1) sending:\n %2").arg(url.toString(), aBuffer.constData()));
260 
261  QString sXml;
262 
263  if (!GetMythDownloadManager()->postAuth(url.toString(), &aBuffer, nullptr, nullptr, &headers))
264  {
265  LOG(VB_GENERAL, LOG_ERR, QString("SOAPClient::SendSOAPRequest: request failed: %1")
266  .arg(url.toString()));
267  }
268  else
269  sXml = QString(aBuffer);
270 
271  // --------------------------------------------------------------
272  // Parse response
273  // --------------------------------------------------------------
274 
275  LOG(VB_UPNP, LOG_DEBUG, "SOAPClient response:\n" +
276  QString("%1\n").arg(sXml));
277 
278  // TODO handle timeout without response correctly.
279 
280  list.clear();
281 
282  QDomDocument doc;
283  int ErrLineNum = 0;
284 
285  if (!doc.setContent(sXml, true, &sErrDesc, &ErrLineNum))
286  {
288  LOG(VB_UPNP, LOG_ERR,
289  QString("SendSOAPRequest(%1) - Invalid response from %2. Error %3: %4. Response: %5")
290  .arg(sMethod, url.toString(),
291  QString::number(nErrCode), sErrDesc, sXml));
292  return xmlResult;
293  }
294 
295  // --------------------------------------------------------------
296  // Is this a valid response?
297  // --------------------------------------------------------------
298 
299  QString sResponseName = sMethod + "Response";
300  QDomNodeList oNodeList =
301  doc.elementsByTagNameNS(m_sNamespace, sResponseName);
302 
303  if (oNodeList.count() == 0)
304  {
305  // --------------------------------------------------------------
306  // Must be a fault... parse it to return reason
307  // --------------------------------------------------------------
308 
309  nErrCode = GetNodeValue(
310  doc, "Envelope/Body/Fault/detail/UPnPResult/errorCode", 500);
311  sErrDesc = GetNodeValue(
312  doc, "Envelope/Body/Fault/detail/UPnPResult/errorDescription", "");
313  if (sErrDesc.isEmpty())
314  sErrDesc = QString("Unknown #%1").arg(nErrCode);
315 
316  QDomNode oNode = FindNode( "Envelope/Body/Fault", doc );
317 
318  oNode = xmlResult.importNode( oNode, true );
319  xmlResult.appendChild( oNode );
320 
321  return xmlResult;
322  }
323 
324  QDomNode oMethod = oNodeList.item(0);
325  if (oMethod.isNull())
326  return xmlResult;
327 
328  QDomNode oNode = oMethod.firstChild();
329  for (; !oNode.isNull(); oNode = oNode.nextSibling())
330  {
331  QDomElement e = oNode.toElement();
332  if (e.isNull())
333  continue;
334 
335  QString sName = e.tagName();
336  QString sValue = "";
337 
338  QDomText oText = oNode.firstChild().toText();
339 
340  if (!oText.isNull())
341  sValue = oText.nodeValue();
342 
343  list.insert(QUrl::fromPercentEncoding(sName.toUtf8()),
344  QUrl::fromPercentEncoding(sValue.toUtf8()));
345  }
346 
347  // Create copy of oMethod that can be used with xmlResult.
348 
349  oMethod = xmlResult.importNode( oMethod.firstChild(), true );
350 
351  // importNode does not attach the new nodes to the document,
352  // do it here.
353 
354  xmlResult.appendChild( oMethod );
355 
356  return xmlResult;
357 }
358 
UPnPResult_MythTV_NoNamespaceGiven
@ UPnPResult_MythTV_NoNamespaceGiven
Definition: upnp.h:86
SOAPClient::GetNodeValue
int GetNodeValue(const QDomNode &node, const QString &sName, int nDefault) const
Gets the named value using QDomNode as the baseNode in the search, returns default if it is not found...
Definition: soapclient.cpp:116
SOAPClient::m_url
QUrl m_url
Definition: soapclient.h:67
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
HTTPRequest::Encode
static QString Encode(const QString &sIn)
Definition: httprequest.cpp:1738
SOAPClient::Init
bool Init(const QUrl &url, const QString &sNamespace, const QString &sControlPath)
SOAPClient Initializer.
Definition: soapclient.cpp:47
upnp.h
mythlogging.h
QStringMap
QMap< QString, QString > QStringMap
Definition: upnputil.h:32
SOAPClient::m_sControlPath
QString m_sControlPath
Definition: soapclient.h:69
UPnPResult_MythTV_XmlParseError
@ UPnPResult_MythTV_XmlParseError
Definition: upnp.h:87
SOAPClient::FindNodeInternal
QDomNode FindNodeInternal(QStringList &sParts, const QDomNode &curNode) const
This is an internal function used to implement FindNode.
Definition: soapclient.cpp:97
soapclient.h
LOC
#define LOC
Definition: soapclient.cpp:26
std
Definition: mythchrono.h:23
SOAPClient::FindNode
QDomNode FindNode(const QString &sName, const QDomNode &baseNode) const
Used by GeNodeValue() methods to find the named node.
Definition: soapclient.cpp:85
SOAPClient::m_sNamespace
QString m_sNamespace
Definition: soapclient.h:68
UPnPResult_Success
@ UPnPResult_Success
Definition: upnp.h:37
SOAPClient::SendSOAPRequest
QDomDocument SendSOAPRequest(const QString &sMethod, QStringMap &list, int &nErrCode, QString &sErrDesc)
Actually sends the sMethod action to the command URL specified in the constructor (url+[/]+sControlPa...
Definition: soapclient.cpp:187
mythdownloadmanager.h
httprequest.h
SOAPClient::SOAPClient
SOAPClient()=default
Empty SOAPClient constructor.
GetMythDownloadManager
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
Definition: mythdownloadmanager.cpp:145