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