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  QStringList parts = sName.split('/', QString::SkipEmptyParts);
85  return FindNodeInternal(parts, baseNode);
86 }
87 
90  QStringList &sParts, const QDomNode &curNode) const
91 {
92  if (sParts.empty())
93  return curNode;
94 
95  QString sName = sParts.front();
96  sParts.pop_front();
97 
98  QDomNode child = curNode.namedItem(sName);
99 
100  if (child.isNull() )
101  sParts.clear();
102 
103  return FindNodeInternal(sParts, child);
104 }
105 
109  const QDomNode &node, const QString &sName, int nDefault) const
110 {
111  QString sValue = GetNodeValue(node, sName, QString::number(nDefault));
112  return sValue.toInt();
113 }
114 
118  const QDomNode &node, const QString &sName, bool bDefault) const
119 {
120  QString sDefault = (bDefault) ? "true" : "false";
121  QString sValue = GetNodeValue(node, sName, sDefault);
122  if (sValue.isEmpty())
123  return bDefault;
124 
125  char ret = sValue[0].toLatin1();
126  switch (ret)
127  {
128  case 't': case 'T': case 'y': case 'Y': case '1':
129  return true;
130  case 'f': case 'F': case 'n': case 'N': case '0':
131  return false;
132  default:
133  return bDefault;
134  }
135 }
136 
140  const QDomNode &node, const QString &sName, const QString &sDefault) const
141 {
142  if (node.isNull())
143  return sDefault;
144 
145  QString sValue = "";
146  QDomNode valNode = FindNode(sName, node);
147 
148  if (!valNode.isNull())
149  {
150  // -=>TODO: Assumes first child is Text Node.
151 
152  QDomText oText = valNode.firstChild().toText();
153 
154  if (!oText.isNull())
155  sValue = oText.nodeValue();
156 
157  return QUrl::fromPercentEncoding(sValue.toUtf8());
158  }
159 
160  return sDefault;
161 }
162 
179 QDomDocument SOAPClient::SendSOAPRequest(const QString &sMethod,
180  QStringMap &list,
181  int &nErrCode,
182  QString &sErrDesc)
183 {
184  QUrl url(m_url);
185 
186  url.setPath(m_sControlPath);
187 
188  nErrCode = UPnPResult_Success;
189  sErrDesc = "";
190 
191  QDomDocument xmlResult;
192  if (m_sNamespace.isEmpty())
193  {
195  sErrDesc = "No namespace given";
196  return xmlResult;
197  }
198 
199  // --------------------------------------------------------------
200  // Add appropriate headers
201  // --------------------------------------------------------------
202  QHash<QByteArray, QByteArray> headers;
203 
204  headers.insert("Content-Type", "text/xml; charset=\"utf-8\"");
205  QString soapHeader = QString("\"%1#%2\"").arg(m_sNamespace).arg(sMethod);
206  headers.insert("SOAPACTION", soapHeader.toUtf8());
207  headers.insert("User-Agent", "Mozilla/9.876 (X11; U; Linux 2.2.12-20 i686, en) "
208  "Gecko/25250101 Netscape/5.432b1");
209  // --------------------------------------------------------------
210  // Build request payload
211  // --------------------------------------------------------------
212 
213  QByteArray aBuffer;
214  QTextStream os( &aBuffer );
215 
216  os.setCodec("UTF-8");
217 
218  os << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
219  os << "<s:Envelope "
220  " s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\""
221  " xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n";
222  os << " <s:Body>\r\n";
223  os << " <u:" << sMethod << " xmlns:u=\"" << m_sNamespace << "\">\r\n";
224 
225  // --------------------------------------------------------------
226  // Add parameters from list
227  // --------------------------------------------------------------
228 
229  for (QStringMap::iterator it = list.begin(); it != list.end(); ++it)
230  {
231  os << " <" << it.key() << ">";
232  os << HTTPRequest::Encode( *it );
233  os << "</" << it.key() << ">\r\n";
234  }
235 
236  os << " </u:" << sMethod << ">\r\n";
237  os << " </s:Body>\r\n";
238  os << "</s:Envelope>\r\n";
239 
240  os.flush();
241 
242  // --------------------------------------------------------------
243  // Perform Request
244  // --------------------------------------------------------------
245 
246  LOG(VB_UPNP, LOG_DEBUG,
247  QString("SOAPClient(%1) sending:\n %2").arg(url.toString()).arg(aBuffer.constData()));
248 
249  QString sXml;
250 
251  if (!GetMythDownloadManager()->postAuth(url.toString(), &aBuffer, nullptr, nullptr, &headers))
252  {
253  LOG(VB_GENERAL, LOG_ERR, QString("SOAPClient::SendSOAPRequest: request failed: %1")
254  .arg(url.toString()));
255  }
256  else
257  sXml = QString(aBuffer);
258 
259  // --------------------------------------------------------------
260  // Parse response
261  // --------------------------------------------------------------
262 
263  LOG(VB_UPNP, LOG_DEBUG, "SOAPClient response:\n" +
264  QString("%1\n").arg(sXml));
265 
266  // TODO handle timeout without response correctly.
267 
268  list.clear();
269 
270  QDomDocument doc;
271  int ErrLineNum = 0;
272 
273  if (!doc.setContent(sXml, true, &sErrDesc, &ErrLineNum))
274  {
276  LOG(VB_UPNP, LOG_ERR,
277  QString("SendSOAPRequest(%1) - Invalid response from %2. Error %3: %4. Response: %5")
278  .arg(sMethod).arg(url.toString())
279  .arg(nErrCode).arg(sErrDesc).arg(sXml));
280  return xmlResult;
281  }
282 
283  // --------------------------------------------------------------
284  // Is this a valid response?
285  // --------------------------------------------------------------
286 
287  QString sResponseName = sMethod + "Response";
288  QDomNodeList oNodeList =
289  doc.elementsByTagNameNS(m_sNamespace, sResponseName);
290 
291  if (oNodeList.count() == 0)
292  {
293  // --------------------------------------------------------------
294  // Must be a fault... parse it to return reason
295  // --------------------------------------------------------------
296 
297  nErrCode = GetNodeValue(
298  doc, "Envelope/Body/Fault/detail/UPnPResult/errorCode", 500);
299  sErrDesc = GetNodeValue(
300  doc, "Envelope/Body/Fault/detail/UPnPResult/errorDescription", "");
301  if (sErrDesc.isEmpty())
302  sErrDesc = QString("Unknown #%1").arg(nErrCode);
303 
304  QDomNode oNode = FindNode( "Envelope/Body/Fault", doc );
305 
306  oNode = xmlResult.importNode( oNode, true );
307  xmlResult.appendChild( oNode );
308 
309  return xmlResult;
310  }
311 
312  QDomNode oMethod = oNodeList.item(0);
313  if (oMethod.isNull())
314  return xmlResult;
315 
316  QDomNode oNode = oMethod.firstChild();
317  for (; !oNode.isNull(); oNode = oNode.nextSibling())
318  {
319  QDomElement e = oNode.toElement();
320  if (e.isNull())
321  continue;
322 
323  QString sName = e.tagName();
324  QString sValue = "";
325 
326  QDomText oText = oNode.firstChild().toText();
327 
328  if (!oText.isNull())
329  sValue = oText.nodeValue();
330 
331  list.insert(QUrl::fromPercentEncoding(sName.toUtf8()),
332  QUrl::fromPercentEncoding(sValue.toUtf8()));
333  }
334 
335  // Create copy of oMethod that can be used with xmlResult.
336 
337  oMethod = xmlResult.importNode( oMethod.firstChild(), true );
338 
339  // importNode does not attach the new nodes to the document,
340  // do it here.
341 
342  xmlResult.appendChild( oMethod );
343 
344  return xmlResult;
345 }
346 
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:91
bool Init(const QUrl &url, const QString &sNamespace, const QString &sControlPath)
SOAPClient Initializer.
Definition: soapclient.cpp:43
QString m_sControlPath
Definition: soapclient.h:69
#define LOC
Definition: soapclient.cpp:22
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:108
static QString Encode(const QString &sIn)
QMap< QString, QString > QStringMap
Definition: upnputil.h:40
QDomNode FindNodeInternal(QStringList &sParts, const QDomNode &curNode) const
This is an internal function used to implement FindNode.
Definition: soapclient.cpp:89
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
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:179
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QDomNode FindNode(const QString &sName, const QDomNode &baseNode) const
Used by GeNodeValue() methods to find the named node.
Definition: soapclient.cpp:81
QString m_sNamespace
Definition: soapclient.h:68
QUrl m_url
Definition: soapclient.h:67
SOAPClient()=default
Empty SOAPClient constructor.