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