QString SSDP::GetHeaderValue( const QStringMap &headers, const QString &sKey, const QString &sDefault ) { QStringMap::const_iterator it = headers.find( sKey.toLower() ); if ( it == headers.end()) return( sDefault ); return *it; }
QDomDocument SOAPClient::SendSOAPRequest(const QString &sMethod, QStringMap &list, int &nErrCode, QString &sErrDesc) { QUrl url(m_url); url.setPath(m_sControlPath); nErrCode = UPnPResult_Success; sErrDesc = ""; QDomDocument xmlResult; if (m_sNamespace.isEmpty()) { nErrCode = UPnPResult_MythTV_NoNamespaceGiven; sErrDesc = "No namespace given"; return xmlResult; } // -------------------------------------------------------------- // Add appropriate headers // -------------------------------------------------------------- QHash<QByteArray, QByteArray> headers; headers.insert("Content-Type", "text/xml; charset=\"utf-8\""); QString soapHeader = QString("\"%1#%2\"").arg(m_sNamespace).arg(sMethod); headers.insert("SOAPACTION", soapHeader.toUtf8()); headers.insert("User-Agent", "Mozilla/9.876 (X11; U; Linux 2.2.12-20 i686, en) " "Gecko/25250101 Netscape/5.432b1"); // -------------------------------------------------------------- // Build request payload // -------------------------------------------------------------- QByteArray aBuffer; QTextStream os( &aBuffer ); os.setCodec("UTF-8"); os << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"; os << "<s:Envelope " " s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"" " xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n"; os << " <s:Body>\r\n"; os << " <u:" << sMethod << " xmlns:u=\"" << m_sNamespace << "\">\r\n"; // -------------------------------------------------------------- // Add parameters from list // -------------------------------------------------------------- for (QStringMap::iterator it = list.begin(); it != list.end(); ++it) { os << " <" << it.key() << ">"; os << HTTPRequest::Encode( *it ); os << "</" << it.key() << ">\r\n"; } os << " </u:" << sMethod << ">\r\n"; os << " </s:Body>\r\n"; os << "</s:Envelope>\r\n"; os.flush(); // -------------------------------------------------------------- // Perform Request // -------------------------------------------------------------- LOG(VB_UPNP, LOG_DEBUG, QString("SOAPClient(%1) sending:\n %2").arg(url.toString()).arg(aBuffer.constData())); QString sXml; if (!GetMythDownloadManager()->postAuth(url.toString(), &aBuffer, NULL, NULL, &headers)) { LOG(VB_GENERAL, LOG_ERR, QString("SOAPClient::SendSOAPRequest: request failed: %1") .arg(url.toString())); } else sXml = QString(aBuffer); // -------------------------------------------------------------- // Parse response // -------------------------------------------------------------- LOG(VB_UPNP, LOG_DEBUG, "SOAPClient response:\n" + QString("%1\n").arg(sXml)); // TODO handle timeout without response correctly. list.clear(); QDomDocument doc; if (!doc.setContent(sXml, true, &sErrDesc, &nErrCode)) { LOG(VB_UPNP, LOG_ERR, QString("SendSOAPRequest( %1 ) - Invalid response from %2") .arg(sMethod).arg(url.toString()) + QString("%1: %2").arg(nErrCode).arg(sErrDesc)); return xmlResult; } // -------------------------------------------------------------- // Is this a valid response? // -------------------------------------------------------------- QString sResponseName = sMethod + "Response"; QDomNodeList oNodeList = doc.elementsByTagNameNS(m_sNamespace, sResponseName); if (oNodeList.count() == 0) { // -------------------------------------------------------------- // Must be a fault... parse it to return reason // -------------------------------------------------------------- nErrCode = GetNodeValue( doc, "Envelope/Body/Fault/detail/UPnPError/errorCode", 500); sErrDesc = GetNodeValue( doc, "Envelope/Body/Fault/detail/UPnPError/errorDescription", ""); if (sErrDesc.isEmpty()) sErrDesc = QString("Unknown #%1").arg(nErrCode); QDomNode oNode = FindNode( "Envelope/Body/Fault", doc ); oNode = xmlResult.importNode( oNode, true ); xmlResult.appendChild( oNode ); return xmlResult; } QDomNode oMethod = oNodeList.item(0); if (oMethod.isNull()) return xmlResult; QDomNode oNode = oMethod.firstChild(); for (; !oNode.isNull(); oNode = oNode.nextSibling()) { QDomElement e = oNode.toElement(); if (e.isNull()) continue; QString sName = e.tagName(); QString sValue = ""; QDomText oText = oNode.firstChild().toText(); if (!oText.isNull()) sValue = oText.nodeValue(); list.insert(QUrl::fromPercentEncoding(sName.toUtf8()), QUrl::fromPercentEncoding(sValue.toUtf8())); } // Create copy of oMethod that can be used with xmlResult. oMethod = xmlResult.importNode( oMethod.firstChild(), true ); // importNode does not attach the new nodes to the document, // do it here. xmlResult.appendChild( oMethod ); return xmlResult; }
/** Actually sends the sMethod action to the command URL specified * in the constructor (url+[/]+sControlPath). * * \param sMethod method to be invoked. e.g. "SetChannel", * "GetConnectionInfoResult" * * \param list Parsed as a series of key value pairs for the input params * and then cleared and used for the output params. * * \param nErrCode set to zero on success, non-zero in case of error. * * \param sErrCode returns error description from device, when applicable. * * \param bInQtThread May be set to true if this is run from within * a QThread with a running an event loop. * * \return Returns a QDomDocument containing output parameters on success. */ QDomDocument SOAPClient::SendSOAPRequest(const QString &sMethod, QStringMap &list, int &nErrCode, QString &sErrDesc, bool bInQtThread) { QUrl url(m_url); url.setPath(m_sControlPath); nErrCode = UPnPResult_Success; sErrDesc = ""; QDomDocument xmlResult; if (m_sNamespace.isEmpty()) { nErrCode = UPnPResult_MythTV_NoNamespaceGiven; sErrDesc = "No namespace given"; return xmlResult; } // -------------------------------------------------------------- // Add appropriate headers // -------------------------------------------------------------- QHttpRequestHeader header("POST", sMethod, 1, 0); header.setValue("CONTENT-TYPE", "text/xml; charset=\"utf-8\"" ); header.setValue("SOAPACTION", QString("\"%1#%2\"").arg(m_sNamespace).arg(sMethod)); // -------------------------------------------------------------- // Build request payload // -------------------------------------------------------------- QByteArray aBuffer; QTextStream os( &aBuffer ); os.setCodec("UTF-8"); os << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"; os << "<s:Envelope " " s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"" " xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n"; os << " <s:Body>\r\n"; os << " <u:" << sMethod << " xmlns:u=\"" << m_sNamespace << "\">\r\n"; // -------------------------------------------------------------- // Add parameters from list // -------------------------------------------------------------- for (QStringMap::iterator it = list.begin(); it != list.end(); ++it) { os << " <" << it.key() << ">"; os << HTTPRequest::Encode( *it ); os << "</" << it.key() << ">\r\n"; } os << " </u:" << sMethod << ">\r\n"; os << " </s:Body>\r\n"; os << "</s:Envelope>\r\n"; os.flush(); // -------------------------------------------------------------- // Perform Request // -------------------------------------------------------------- QBuffer buff(&aBuffer); LOG(VB_UPNP, LOG_DEBUG, QString("SOAPClient(%1) sending:\n").arg(url.toString()) + header.toString() + QString("\n%1\n").arg(aBuffer.constData())); QString sXml = HttpComms::postHttp(url, &header, &buff, // QIODevice* 10000, // ms -- Technically should be 30ms per spec 3, // retries 0, // redirects false, // allow gzip NULL, // login bInQtThread, QString() // userAgent, UPnP/1.0 very strict on // format if set ); // -------------------------------------------------------------- // Parse response // -------------------------------------------------------------- LOG(VB_UPNP, LOG_DEBUG, "SOAPClient response:\n" + QString("%1\n").arg(sXml)); // TODO handle timeout without response correctly. list.clear(); QDomDocument doc; if (!doc.setContent(sXml, true, &sErrDesc, &nErrCode)) { LOG(VB_UPNP, LOG_ERR, QString("SendSOAPRequest( %1 ) - Invalid response from %2") .arg(sMethod).arg(url.toString()) + QString("%1: %2").arg(nErrCode).arg(sErrDesc)); return xmlResult; } // -------------------------------------------------------------- // Is this a valid response? // -------------------------------------------------------------- QString sResponseName = sMethod + "Response"; QDomNodeList oNodeList = doc.elementsByTagNameNS(m_sNamespace, sResponseName); if (oNodeList.count() == 0) { // -------------------------------------------------------------- // Must be a fault... parse it to return reason // -------------------------------------------------------------- nErrCode = GetNodeValue( doc, "Envelope/Body/Fault/detail/UPnPError/errorCode", 500); sErrDesc = GetNodeValue( doc, "Envelope/Body/Fault/detail/UPnPError/errorDescription", ""); if (sErrDesc.isEmpty()) sErrDesc = QString("Unknown #%1").arg(nErrCode); QDomNode oNode = FindNode( "Envelope/Body/Fault", doc ); oNode = xmlResult.importNode( oNode, true ); xmlResult.appendChild( oNode ); return xmlResult; } QDomNode oMethod = oNodeList.item(0); if (oMethod.isNull()) return xmlResult; QDomNode oNode = oMethod.firstChild(); for (; !oNode.isNull(); oNode = oNode.nextSibling()) { QDomElement e = oNode.toElement(); if (e.isNull()) continue; QString sName = e.tagName(); QString sValue = ""; QDomText oText = oNode.firstChild().toText(); if (!oText.isNull()) sValue = oText.nodeValue(); list.insert(QUrl::fromPercentEncoding(sName.toUtf8()), QUrl::fromPercentEncoding(sValue.toUtf8())); } // Create copy of oMethod that can be used with xmlResult. oMethod = xmlResult.importNode( oMethod.firstChild(), true ); // importNode does not attach the new nodes to the document, // do it here. xmlResult.appendChild( oMethod ); return xmlResult; }
QVariant MethodInfo::Invoke( Service *pService, const QStringMap &reqParams ) { HttpRedirectException exception; bool bExceptionThrown = false; QStringMap lowerParams; if (!pService) throw; // Change params to lower case for case-insensitive comparison QStringMap::const_iterator it = reqParams.begin(); for (; it != reqParams.end(); ++it) { lowerParams[it.key().toLower()] = *it; } // -------------------------------------------------------------- // Provide actual parameters received to method // -------------------------------------------------------------- pService->m_parsedParams = lowerParams.keys(); QList<QByteArray> paramNames = m_oMethod.parameterNames(); QList<QByteArray> paramTypes = m_oMethod.parameterTypes(); // ---------------------------------------------------------------------- // Create Parameter array (Can't have more than _MAX_PARAMS parameters).... // switched to static array for performance. // ---------------------------------------------------------------------- void *param[ _MAX_PARAMS ]; int types[ _MAX_PARAMS ]; memset( param, 0, _MAX_PARAMS * sizeof(void *)); memset( types, 0, _MAX_PARAMS * sizeof(int)); try { // -------------------------------------------------------------- // Add a place for the Return value // -------------------------------------------------------------- int nRetIdx = QMetaType::type( m_oMethod.typeName() ); if (nRetIdx != 0) { param[ 0 ] = QMetaType::create( nRetIdx ); types[ 0 ] = nRetIdx; } else { param[ 0 ] = nullptr; types[ 0 ] = 0; } // -------------------------------------------------------------- // Fill in parameters from request values // -------------------------------------------------------------- for( int nIdx = 0; nIdx < paramNames.length(); nIdx++ ) { QString sValue = lowerParams[ paramNames[ nIdx ].toLower() ]; QString sParamType = paramTypes[ nIdx ]; int nId = QMetaType::type( paramTypes[ nIdx ] ); void *pParam = nullptr; if (nId != 0) { pParam = QMetaType::create( nId ); } else { LOG(VB_GENERAL, LOG_ERR, QString("MethodInfo::Invoke - Type unknown '%1'") .arg(sParamType)); } types[nIdx+1] = nId; param[nIdx+1] = pService->ConvertToParameterPtr( nId, sParamType, pParam, sValue ); } #if 0 QThread *currentThread = QThread::currentThread(); QThread *objectThread = pService->thread(); if (currentThread == objectThread) LOG(VB_HTTP, LOG_DEBUG, "*** Threads are same ***"); else LOG(VB_HTTP, LOG_DEBUG, "*** Threads are Different!!! ***"); #endif pService->qt_metacall( QMetaObject::InvokeMetaMethod, m_nMethodIndex, param ); // -------------------------------------------------------------- // Delete param array, skip return parameter since not dynamically // created. // -------------------------------------------------------------- for (int nIdx=1; nIdx < paramNames.length()+1; nIdx++) { if ((types[ nIdx ] != 0) && (param[ nIdx ] != nullptr)) QMetaType::destroy( types[ nIdx ], param[ nIdx ] ); } } catch (QString &sMsg) { LOG(VB_GENERAL, LOG_ERR, QString("MethodInfo::Invoke - An Exception Occurred: %1") .arg(sMsg)); if ((types[ 0 ] != 0) && (param[ 0 ] != nullptr )) QMetaType::destroy( types[ 0 ], param[ 0 ] ); throw; } catch (HttpRedirectException &ex) { bExceptionThrown = true; exception = ex; } catch (...) { LOG(VB_GENERAL, LOG_INFO, "MethodInfo::Invoke - An Exception Occurred" ); } // -------------------------------------------------------------- // return the result after converting to a QVariant // -------------------------------------------------------------- QVariant vReturn; if ( param[ 0 ] != nullptr) { vReturn = pService->ConvertToVariant( types[ 0 ], param[ 0 ] ); if (types[ 0 ] != 0) QMetaType::destroy( types[ 0 ], param[ 0 ] ); } // -------------------------------------------------------------- // Re-throw exception if needed. // -------------------------------------------------------------- if (bExceptionThrown) throw exception; return vReturn; }
bool SOAPClient::SendSOAPRequest( const QString &sMethod, QStringMap &list, int &nErrCode, QString &sErrDesc, bool bInQtThread ) { QUrl url( m_url ); url.setPath( m_sControlPath ); // -------------------------------------------------------------- // Add appropriate headers // -------------------------------------------------------------- QHttpRequestHeader header; header.setValue("CONTENT-TYPE", "text/xml; charset=\"utf-8\"" ); header.setValue("SOAPACTION" , QString( "\"%1#GetConnectionInfo\"" ) .arg( m_sNamespace )); // -------------------------------------------------------------- // Build request payload // -------------------------------------------------------------- QByteArray aBuffer; QTextStream os( &aBuffer ); os << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"; os << "<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n"; os << " <s:Body>\r\n"; os << " <u:" << sMethod << " xmlns:u=\"" << m_sNamespace << "\">\r\n"; // -------------------------------------------------------------- // Add parameters from list // -------------------------------------------------------------- for ( QStringMap::iterator it = list.begin(); it != list.end(); ++it ) { os << " <" << it.key() << ">"; os << HTTPRequest::Encode( *it ); os << "</" << it.key() << ">\r\n"; } os << " </u:" << sMethod << ">\r\n"; os << " </s:Body>\r\n"; os << "</s:Envelope>\r\n"; os.flush(); // -------------------------------------------------------------- // Perform Request // -------------------------------------------------------------- QBuffer buff( &aBuffer ); QString sXml = HttpComms::postHttp( url, &header, (QIODevice *)&buff, 10000, // ms 3, // retries 0, // redirects false, // allow gzip NULL, // login bInQtThread ); // -------------------------------------------------------------- // Parse response // -------------------------------------------------------------- list.clear(); QDomDocument doc; if ( !doc.setContent( sXml, true, &sErrDesc, &nErrCode )) { VERBOSE( VB_UPNP, QString( "MythXMLClient::SendSOAPRequest( %1 ) - Invalid response from %2" ) .arg( sMethod ) .arg( url.toString() )); return false; } // -------------------------------------------------------------- // Is this a valid response? // -------------------------------------------------------------- QString sResponseName = sMethod + "Response"; QDomNodeList oNodeList = doc.elementsByTagNameNS( m_sNamespace, sResponseName ); if (oNodeList.count() > 0) { QDomNode oMethod = oNodeList.item(0); if (!oMethod.isNull()) { for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull(); oNode = oNode.nextSibling() ) { QDomElement e = oNode.toElement(); if (!e.isNull()) { QString sName = e.tagName(); QString sValue = ""; QDomText oText = oNode.firstChild().toText(); if (!oText.isNull()) sValue = oText.nodeValue(); list.insert(QUrl::fromPercentEncoding(sName.toUtf8()), QUrl::fromPercentEncoding(sValue.toUtf8())); } } } return true; } // -------------------------------------------------------------- // Must be a fault... parse it to return reason // -------------------------------------------------------------- nErrCode = GetNodeValue( doc, "Envelope/Body/Fault/detail/UPnPResult/errorCode" , 500 ); sErrDesc = GetNodeValue( doc, "Envelope/Body/Fault/detail/UPnPResult/errorDescription", QString( "Unknown" )); return false; }
bool ServerSideScripting::EvaluatePage( QTextStream *pOutStream, const QString &sFileName, HTTPRequest *pRequest) { try { ScriptInfo *pInfo = NULL; // ------------------------------------------------------------------ // See if page has already been loaded // ------------------------------------------------------------------ pInfo = GetLoadedScript( sFileName ); // ------------------------------------------------------------------ // Load Script File and Create Function // ------------------------------------------------------------------ QFileInfo fileInfo ( sFileName ); QDateTime dtLastModified = fileInfo.lastModified(); Lock(); if ((pInfo == NULL) || (pInfo->m_dtTimeStamp != dtLastModified )) { QString sCode = CreateMethodFromFile( sFileName ); QScriptValue func = m_engine.evaluate( sCode, sFileName ); if ( m_engine.hasUncaughtException() ) { LOG(VB_GENERAL, LOG_ERR, QString("Error Loading QSP File: %1 - (line %2) %3") .arg(sFileName) .arg(m_engine.uncaughtExceptionLineNumber()) .arg(m_engine.uncaughtException().toString())); Unlock(); return false; } if (pInfo != NULL) { pInfo->m_oFunc = func; pInfo->m_dtTimeStamp = dtLastModified; } else { pInfo = new ScriptInfo( func, dtLastModified ); m_mapScripts[ sFileName ] = pInfo; } } // ------------------------------------------------------------------ // Build array of arguments passed to script // ------------------------------------------------------------------ QStringMap mapParams = pRequest->m_mapParams; // Valid characters for object property names must contain only // word characters and numbers, _ and $ // They must not start with a number - to simplify the regexp, we // restrict the first character to the English alphabet QRegExp validChars = QRegExp("^([a-zA-Z]|_|\\$)(\\w|\\$)+$"); QVariantMap params; QMap<QString, QString>::const_iterator it = mapParams.begin(); QString prevArrayName = ""; QVariantMap array; for (; it != mapParams.end(); ++it) { QString key = it.key(); QVariant value = QVariant(it.value()); // PHP Style parameter array if (key.contains("[")) { QString arrayName = key.section('[',0,0); QString arrayKey = key.section('[',1,1); arrayKey.chop(1); // Remove trailing ] if (prevArrayName != arrayName) // New or different array { if (!array.isEmpty()) { params.insert(prevArrayName, QVariant(array)); array.clear(); } prevArrayName = arrayName; } if (!validChars.exactMatch(arrayKey)) // Discard anything that isn't valid for now continue; array.insert(arrayKey, value); if ((it + 1) != mapParams.end()) continue; } if (!array.isEmpty()) { params.insert(prevArrayName, QVariant(array)); array.clear(); } // End Array handling if (!validChars.exactMatch(key)) // Discard anything that isn't valid for now continue; params.insert(key, value); } // ------------------------------------------------------------------ // Build array of request headers // ------------------------------------------------------------------ QStringMap mapHeaders = pRequest->m_mapHeaders; QVariantMap requestHeaders; for (it = mapHeaders.begin(); it != mapHeaders.end(); ++it) { QString key = it.key(); key = key.replace('-', '_'); // May be other valid chars in a request header that we need to replace QVariant value = QVariant(it.value()); if (!validChars.exactMatch(key)) // Discard anything that isn't valid for now continue; requestHeaders.insert(key, value); } // ------------------------------------------------------------------ // Build array of information from the server e.g. client IP // See RFC 3875 - The Common Gateway Interface // ------------------------------------------------------------------ QVariantMap serverVars; serverVars.insert("REMOTE_ADDR", QVariant(pRequest->GetPeerAddress())); serverVars.insert("SERVER_ADDR", QVariant(pRequest->GetHostAddress())); serverVars.insert("SERVER_PROTOCOL", QVariant(pRequest->GetRequestProtocol())); serverVars.insert("SERVER_SOFTWARE", QVariant(HttpServer::GetServerVersion())); QHostAddress clientIP = QHostAddress(pRequest->GetPeerAddress()); QHostAddress serverIP = QHostAddress(pRequest->GetHostAddress()); if (clientIP.protocol() == QAbstractSocket::IPv4Protocol) { serverVars.insert("IP_PROTOCOL", "IPv4"); } else if (clientIP.protocol() == QAbstractSocket::IPv6Protocol) { serverVars.insert("IP_PROTOCOL", "IPv6"); } if (((clientIP.protocol() == QAbstractSocket::IPv4Protocol) && (clientIP.isInSubnet(QHostAddress("172.16.0.0"), 12) || clientIP.isInSubnet(QHostAddress("192.168.0.0"), 16) || clientIP.isInSubnet(QHostAddress("10.0.0.0"), 8))) || ((clientIP.protocol() == QAbstractSocket::IPv6Protocol) && clientIP.isInSubnet(serverIP, 64))) // default subnet size is assumed to be /64 { serverVars.insert("CLIENT_NETWORK", "local"); } else { serverVars.insert("CLIENT_NETWORK", "remote"); } // ------------------------------------------------------------------ // Add the arrays (objects) we've just created to the global scope // They may be accessed as 'Server.REMOTE_ADDR' // ------------------------------------------------------------------ m_engine.globalObject().setProperty("Parameters", m_engine.toScriptValue(params)); m_engine.globalObject().setProperty("RequestHeaders", m_engine.toScriptValue(requestHeaders)); m_engine.globalObject().setProperty("Server", m_engine.toScriptValue(serverVars)); // ------------------------------------------------------------------ // Execute function to render output // ------------------------------------------------------------------ OutputStream outStream( pOutStream ); QScriptValueList args; args << m_engine.newQObject( &outStream ); args << m_engine.toScriptValue(params); QScriptValue ret = pInfo->m_oFunc.call( QScriptValue(), args ); if (ret.isError()) { QScriptValue lineNo = ret.property( "lineNumber" ); LOG(VB_GENERAL, LOG_ERR, QString("Error calling QSP File: %1(%2) - %3") .arg(sFileName) .arg( lineNo.toInt32 () ) .arg( ret .toString() )); Unlock(); return false; } if (m_engine.hasUncaughtException()) { LOG(VB_GENERAL, LOG_ERR, QString("Error calling QSP File: %1(%2) - %3") .arg(sFileName) .arg(m_engine.uncaughtExceptionLineNumber() ) .arg(m_engine.uncaughtException().toString())); Unlock(); return false; } Unlock(); } catch(...) { LOG(VB_GENERAL, LOG_ERR, QString("Exception while evaluating QSP File: %1") .arg(sFileName)); Unlock(); return false; } return true; }