/** * @file llxmlrpclistener.cpp * @author Nat Goodspeed * @date 2009-03-18 * @brief Implementation for llxmlrpclistener. * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ // Precompiled header #include "llviewerprecompiledheaders.h" // associated header #include "llxmlrpclistener.h" // STL headers #include <map> #include <set> #include "curl/curl.h" // other Linden headers #include "llerror.h" #include "lleventcoro.h" #include "stringize.h" #include "llxmlrpctransaction.h" #include "llsecapi.h" #if LL_WINDOWS #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally #endif template <typename STATUS> class StatusMapperBase { typedef std::map<STATUS, std::string> MapType; public: StatusMapperBase(const std::string& desc): mDesc(desc) {} std::string lookup(STATUS status) const { typename MapType::const_iterator found = mMap.find(status); if (found != mMap.end()) { return found->second; } return STRINGIZE("<unknown " << mDesc << " " << status << ">"); } protected: std::string mDesc; MapType mMap; }; class StatusMapper: public StatusMapperBase<LLXMLRPCTransaction::EStatus> { public: StatusMapper(): StatusMapperBase<LLXMLRPCTransaction::EStatus>("Status") { mMap[LLXMLRPCTransaction::StatusNotStarted] = "NotStarted"; mMap[LLXMLRPCTransaction::StatusStarted] = "Started"; mMap[LLXMLRPCTransaction::StatusDownloading] = "Downloading"; mMap[LLXMLRPCTransaction::StatusComplete] = "Complete"; mMap[LLXMLRPCTransaction::StatusCURLError] = "CURLError"; mMap[LLXMLRPCTransaction::StatusXMLRPCError] = "XMLRPCError"; mMap[LLXMLRPCTransaction::StatusOtherError] = "OtherError"; } }; static const StatusMapper sStatusMapper; class CURLcodeMapper: public StatusMapperBase<CURLcode> { public: CURLcodeMapper(): StatusMapperBase<CURLcode>("CURLcode") { // from curl.h // skip the "CURLE_" prefix for each of these strings #define def(sym) (mMap[sym] = &#sym[6]) def(CURLE_OK); def(CURLE_UNSUPPORTED_PROTOCOL); /* 1 */ def(CURLE_FAILED_INIT); /* 2 */ def(CURLE_URL_MALFORMAT); /* 3 */ def(CURLE_COULDNT_RESOLVE_PROXY); /* 5 */ def(CURLE_COULDNT_RESOLVE_HOST); /* 6 */ def(CURLE_COULDNT_CONNECT); /* 7 */ def(CURLE_PARTIAL_FILE); /* 18 */ def(CURLE_HTTP_RETURNED_ERROR); /* 22 */ def(CURLE_WRITE_ERROR); /* 23 */ def(CURLE_UPLOAD_FAILED); /* 25 - failed upload "command" */ def(CURLE_READ_ERROR); /* 26 - could open/read from file */ def(CURLE_OUT_OF_MEMORY); /* 27 */ /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error instead of a memory allocation error if CURL_DOES_CONVERSIONS is defined */ def(CURLE_OPERATION_TIMEDOUT); /* 28 - the timeout time was reached */ def(CURLE_HTTP_RANGE_ERROR); /* 33 - RANGE "command" didn't work */ def(CURLE_HTTP_POST_ERROR); /* 34 */ def(CURLE_SSL_CONNECT_ERROR); /* 35 - wrong when connecting with SSL */ def(CURLE_BAD_DOWNLOAD_RESUME); /* 36 - couldn't resume download */ def(CURLE_FILE_COULDNT_READ_FILE); /* 37 */ def(CURLE_LIBRARY_NOT_FOUND); /* 40 */ def(CURLE_FUNCTION_NOT_FOUND); /* 41 */ def(CURLE_ABORTED_BY_CALLBACK); /* 42 */ def(CURLE_BAD_FUNCTION_ARGUMENT); /* 43 */ def(CURLE_INTERFACE_FAILED); /* 45 - CURLOPT_INTERFACE failed */ def(CURLE_TOO_MANY_REDIRECTS ); /* 47 - catch endless re-direct loops */ def(CURLE_SSL_PEER_CERTIFICATE); /* 51 - peer's certificate wasn't ok */ def(CURLE_GOT_NOTHING); /* 52 - when this is a specific error */ def(CURLE_SSL_ENGINE_NOTFOUND); /* 53 - SSL crypto engine not found */ def(CURLE_SSL_ENGINE_SETFAILED); /* 54 - can not set SSL crypto engine as default */ def(CURLE_SEND_ERROR); /* 55 - failed sending network data */ def(CURLE_RECV_ERROR); /* 56 - failure in receiving network data */ def(CURLE_SSL_CERTPROBLEM); /* 58 - problem with the local certificate */ def(CURLE_SSL_CIPHER); /* 59 - couldn't use specified cipher */ def(CURLE_SSL_CACERT); /* 60 - problem with the CA cert (path?) */ def(CURLE_BAD_CONTENT_ENCODING); /* 61 - Unrecognized transfer encoding */ def(CURLE_FILESIZE_EXCEEDED); /* 63 - Maximum file size exceeded */ def(CURLE_SEND_FAIL_REWIND); /* 65 - Sending the data requires a rewind that failed */ def(CURLE_SSL_ENGINE_INITFAILED); /* 66 - failed to initialise ENGINE */ def(CURLE_LOGIN_DENIED); /* 67 - user); password or similar was not accepted and we failed to login */ def(CURLE_CONV_FAILED); /* 75 - conversion failed */ def(CURLE_CONV_REQD); /* 76 - caller must register conversion callbacks using curl_easy_setopt options CURLOPT_CONV_FROM_NETWORK_FUNCTION); CURLOPT_CONV_TO_NETWORK_FUNCTION); and CURLOPT_CONV_FROM_UTF8_FUNCTION */ def(CURLE_SSL_CACERT_BADFILE); /* 77 - could not load CACERT file); missing or wrong format */ def(CURLE_REMOTE_FILE_NOT_FOUND); /* 78 - remote file not found */ def(CURLE_SSH); /* 79 - error from the SSH layer); somewhat generic so the error message will be of interest when this has happened */ def(CURLE_SSL_SHUTDOWN_FAILED); /* 80 - Failed to shut down the SSL connection */ #undef def } }; static const CURLcodeMapper sCURLcodeMapper; /** * Capture an outstanding LLXMLRPCTransaction and poll it periodically until * done. * * The sequence is: * # Instantiate Poller, which instantiates, populates and initiates an * LLXMLRPCTransaction. Poller self-registers on the LLEventPump named * "mainloop". * # "mainloop" is conventionally pumped once per frame. On each such call, * Poller checks its LLXMLRPCTransaction for completion. * # When the LLXMLRPCTransaction completes, Poller collects results (if any) * and sends notification. * # The tricky part: Poller frees itself (and thus its LLXMLRPCTransaction) * when done. The only external reference to it is the connection to the * "mainloop" LLEventPump. */ class Poller { public: /// Validate the passed request for required fields, then use it to /// populate an XMLRPC_REQUEST and an associated LLXMLRPCTransaction. Send /// the request. Poller(const LLSD& command): mReqID(command), mUri(command["uri"]), mMethod(command["method"]), mReplyPump(command["reply"]) { // LL_ERRS if any of these keys are missing or empty if (mUri.empty() || mMethod.empty() || mReplyPump.empty()) { LL_ERRS("LLXMLRPCListener") << "Some params are missing: " << "reply: '" << mReplyPump << "', " << "method: '" << mMethod << "', " << "uri: '" << mUri << "'" << LL_ENDL; } LLSD request_params = LLSD::emptyMap(); LLSD params = command.get("params"); if (params.isMap()) { for (LLSD::map_const_iterator pi(params.beginMap()), pend(params.endMap()); pi != pend; ++pi) { std::string name(pi->first); LLSD param(pi->second); switch (param.type()) { case LLSD::TypeString: case LLSD::TypeInteger: case LLSD::TypeReal: request_params.insert(name, param); break; case LLSD::TypeBoolean: request_params.insert(name, param.asInteger()); break; default: LL_ERRS("LLXMLRPCListener") << mMethod << " request param '" << name << "' has unknown type: " << param << LL_ENDL; } } } LLSD options = command.get("options"); if (options.isArray()) { request_params.insert("options", options); } LLSD http_params = command.get("http_params"); mTransaction.reset(new LLXMLRPCTransaction(mUri, mMethod, request_params, http_params)); mPreviousStatus = mTransaction->status(NULL); // Now ensure that we get regular callbacks to poll for completion. mBoundListener = LLEventPumps::instance(). obtain("mainloop"). listen(LLEventPump::ANONYMOUS, boost::bind(&Poller::poll, this, _1)); LL_INFOS("LLXMLRPCListener") << mMethod << " request sent to " << mUri << LL_ENDL; } /// called by "mainloop" LLEventPump bool poll(const LLSD&) { bool done = mTransaction->process(); CURLcode curlcode; LLXMLRPCTransaction::EStatus status; { // LLXMLRPCTransaction::status() is defined to accept int* rather // than CURLcode*. I don't feel the urge to fix the signature, but // we want a CURLcode rather than an int. So fetch it as a local // int, but then assign to a CURLcode for the remainder of this // method. int curlint; status = mTransaction->status(&curlint); curlcode = CURLcode(curlint); } LLSD data(mReqID.makeResponse()); data["status"] = sStatusMapper.lookup(status); data["errorcode"] = sCURLcodeMapper.lookup(curlcode); data["error"] = ""; data["transfer_rate"] = 0.0; LLEventPump& replyPump(LLEventPumps::instance().obtain(mReplyPump)); if (!done) { // Not done yet, carry on. if (status == LLXMLRPCTransaction::StatusDownloading && status != mPreviousStatus) { // If a response has been received, send the // 'downloading' status if it hasn't been sent. replyPump.post(data); } mPreviousStatus = status; return false; } // Here the transaction is complete. Check status. data["error"] = mTransaction->statusMessage(); data["transfer_rate"] = mTransaction->transferRate(); LL_INFOS("LLXMLRPCListener") << mMethod << " result from " << mUri << ": status " << data["status"].asString() << ", errorcode " << data["errorcode"].asString() << " (" << data["error"].asString() << ")" << LL_ENDL; switch (curlcode) { #if CURLE_SSL_PEER_CERTIFICATE != CURLE_SSL_CACERT case CURLE_SSL_PEER_CERTIFICATE: #endif case CURLE_SSL_CACERT: data["certificate"] = mTransaction->getErrorCertData(); break; default: break; } // values of 'curlcode': // CURLE_COULDNT_RESOLVE_HOST, // CURLE_SSL_PEER_CERTIFICATE, // CURLE_SSL_CACERT, // CURLE_SSL_CONNECT_ERROR. // Given 'message', need we care? if (status == LLXMLRPCTransaction::StatusComplete) { // Success! Retrieve response data. data["responses"] = mTransaction->response(); } // whether successful or not, send reply on requested LLEventPump replyPump.post(data); // need to wake up the loginCoro now llcoro::suspend(); // Because mTransaction is a std::unique_ptr, deleting this object // frees our LLXMLRPCTransaction object. // Because mBoundListener is an LLTempBoundListener, deleting this // object disconnects it from "mainloop". // *** MUST BE LAST *** delete this; return false; } private: const LLReqID mReqID; const std::string mUri; const std::string mMethod; const std::string mReplyPump; LLTempBoundListener mBoundListener; std::unique_ptr<LLXMLRPCTransaction> mTransaction; LLXMLRPCTransaction::EStatus mPreviousStatus; // To detect state changes. }; LLXMLRPCListener::LLXMLRPCListener(const std::string& pumpname) : mBoundListener(LLEventPumps::instance().obtain(pumpname).listen ( "LLXMLRPCListener", [&](const LLSD& command) -> bool { // Allocate a new heap Poller, but do not save a pointer to it. Poller // will check its own status and free itself on completion of the request. (new Poller(command)); // Conventional event listener return return false; } )) { }