phoenix-firestorm/indra/newview/llxmlrpctransaction.cpp

622 lines
16 KiB
C++

/**
* @file llxmlrpctransaction.cpp
* @brief LLXMLRPCTransaction and related class implementations
*
* $LicenseInfo:firstyear=2006&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$
*/
#include "llviewerprecompiledheaders.h"
// include this to get winsock2 because openssl attempts to include winsock1
#include "llwin32headerslean.h"
#include <openssl/x509_vfy.h>
#include <openssl/ssl.h>
#include "llsecapi.h"
#include "llxmlrpctransaction.h"
#include "llxmlrpclistener.h"
#include "httpcommon.h"
#include "llhttpconstants.h"
#include "httprequest.h"
#include "httpoptions.h"
#include "httpheaders.h"
#include "bufferarray.h"
#include "llversioninfo.h"
#include "llviewercontrol.h"
// Have to include these last to avoid queue redefinition!
#include <xmlrpc-epi/xmlrpc.h>
#include "llappviewer.h"
#include "lltrans.h"
#include "boost/move/unique_ptr.hpp"
namespace boost
{
using ::boost::movelib::unique_ptr; // move unique_ptr into the boost namespace.
}
// Static instance of LLXMLRPCListener declared here so that every time we
// bring in this code, we instantiate a listener. If we put the static
// instance of LLXMLRPCListener into llxmlrpclistener.cpp, the linker would
// simply omit llxmlrpclistener.o, and shouting on the LLEventPump would do
// nothing.
static LLXMLRPCListener listener("LLXMLRPCTransaction");
LLXMLRPCValue LLXMLRPCValue::operator[](const char* id) const
{
return LLXMLRPCValue(XMLRPC_VectorGetValueWithID(mV, id));
}
std::string LLXMLRPCValue::asString() const
{
const char* s = XMLRPC_GetValueString(mV);
return s ? s : "";
}
int LLXMLRPCValue::asInt() const { return XMLRPC_GetValueInt(mV); }
bool LLXMLRPCValue::asBool() const { return XMLRPC_GetValueBoolean(mV) != 0; }
double LLXMLRPCValue::asDouble() const { return XMLRPC_GetValueDouble(mV); }
LLXMLRPCValue LLXMLRPCValue::rewind()
{
return LLXMLRPCValue(XMLRPC_VectorRewind(mV));
}
LLXMLRPCValue LLXMLRPCValue::next()
{
return LLXMLRPCValue(XMLRPC_VectorNext(mV));
}
bool LLXMLRPCValue::isValid() const
{
return mV != NULL;
}
LLXMLRPCValue LLXMLRPCValue::createArray()
{
return LLXMLRPCValue(XMLRPC_CreateVector(NULL, xmlrpc_vector_array));
}
LLXMLRPCValue LLXMLRPCValue::createStruct()
{
return LLXMLRPCValue(XMLRPC_CreateVector(NULL, xmlrpc_vector_struct));
}
void LLXMLRPCValue::append(LLXMLRPCValue& v)
{
XMLRPC_AddValueToVector(mV, v.mV);
}
void LLXMLRPCValue::appendString(const std::string& v)
{
XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueString(NULL, v.c_str(), 0));
}
void LLXMLRPCValue::appendInt(int v)
{
XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueInt(NULL, v));
}
void LLXMLRPCValue::appendBool(bool v)
{
XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueBoolean(NULL, v));
}
void LLXMLRPCValue::appendDouble(double v)
{
XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueDouble(NULL, v));
}
void LLXMLRPCValue::append(const char* id, LLXMLRPCValue& v)
{
XMLRPC_SetValueID(v.mV, id, 0);
XMLRPC_AddValueToVector(mV, v.mV);
}
void LLXMLRPCValue::appendString(const char* id, const std::string& v)
{
XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueString(id, v.c_str(), 0));
}
void LLXMLRPCValue::appendInt(const char* id, int v)
{
XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueInt(id, v));
}
void LLXMLRPCValue::appendBool(const char* id, bool v)
{
XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueBoolean(id, v));
}
void LLXMLRPCValue::appendDouble(const char* id, double v)
{
XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueDouble(id, v));
}
void LLXMLRPCValue::cleanup()
{
XMLRPC_CleanupValue(mV);
mV = NULL;
}
XMLRPC_VALUE LLXMLRPCValue::getValue() const
{
return mV;
}
class LLXMLRPCTransaction::Handler : public LLCore::HttpHandler
{
public:
Handler(LLCore::HttpRequest::ptr_t &request, LLXMLRPCTransaction::Impl *impl);
virtual ~Handler();
virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
typedef boost::shared_ptr<LLXMLRPCTransaction::Handler> ptr_t;
private:
LLXMLRPCTransaction::Impl *mImpl;
LLCore::HttpRequest::ptr_t mRequest;
};
class LLXMLRPCTransaction::Impl
{
public:
typedef LLXMLRPCTransaction::EStatus EStatus;
LLCore::HttpRequest::ptr_t mHttpRequest;
EStatus mStatus;
CURLcode mCurlCode;
std::string mStatusMessage;
std::string mStatusURI;
LLCore::HttpResponse::TransferStats::ptr_t mTransferStats;
Handler::ptr_t mHandler;
LLCore::HttpHandle mPostH;
std::string mURI;
std::string mProxyAddress;
std::string mResponseText;
XMLRPC_REQUEST mResponse;
std::string mCertStore;
LLSD mErrorCertData;
Impl(const std::string& uri, XMLRPC_REQUEST request, bool useGzip, const LLSD& httpParams);
Impl(const std::string& uri,
const std::string& method, LLXMLRPCValue params, bool useGzip);
~Impl();
bool process();
void setStatus(EStatus code, const std::string& message = "", const std::string& uri = "");
void setHttpStatus(const LLCore::HttpStatus &status);
private:
void init(XMLRPC_REQUEST request, bool useGzip, const LLSD& httpParams);
};
LLXMLRPCTransaction::Handler::Handler(LLCore::HttpRequest::ptr_t &request,
LLXMLRPCTransaction::Impl *impl) :
mImpl(impl),
mRequest(request)
{
}
LLXMLRPCTransaction::Handler::~Handler()
{
}
void LLXMLRPCTransaction::Handler::onCompleted(LLCore::HttpHandle handle,
LLCore::HttpResponse * response)
{
LLCore::HttpStatus status = response->getStatus();
if (!status)
{
mImpl->setHttpStatus(status);
LLSD errordata = status.getErrorData();
mImpl->mErrorCertData = errordata;
if ((status.toULong() != CURLE_SSL_PEER_CERTIFICATE) &&
(status.toULong() != CURLE_SSL_CACERT))
{
// if we have a curl error that's not already been handled
// (a non cert error), then generate the warning message as
// appropriate
LL_WARNS() << "LLXMLRPCTransaction error "
<< status.toHex() << ": " << status.toString() << LL_ENDL;
LL_WARNS() << "LLXMLRPCTransaction request URI: "
<< mImpl->mURI << LL_ENDL;
}
return;
}
mImpl->setStatus(LLXMLRPCTransaction::StatusComplete);
mImpl->mTransferStats = response->getTransferStats();
// the contents of a buffer array are potentially noncontiguous, so we
// will need to copy them into an contiguous block of memory for XMLRPC.
LLCore::BufferArray *body = response->getBody();
//<FS:Beq> crash in transaction completion (see bugsplat https://app.bugsplat.com/v2/keycrash?database=fs_windows&stackKeyId=13)
if(body == nullptr)
{
// status should be 0 but log it anyway
// everything past this point appears to depend on the body data
// so log the issues and return.
LL_WARNS()
<< "LLXMLRPCTransaction response has no body. status="
<< status.toHex() << ": " << status.toString() << LL_ENDL;
LL_WARNS()
<< "LLXMLRPCTransaction request URI: "
<< mImpl->mURI << LL_ENDL;
return;
}
//</FS:Beq>
char * bodydata = new char[body->size()];
body->read(0, bodydata, body->size());
mImpl->mResponse = XMLRPC_REQUEST_FromXML(bodydata, body->size(), 0);
delete[] bodydata;
bool hasError = false;
bool hasFault = false;
int faultCode = 0;
std::string faultString;
LLXMLRPCValue error(XMLRPC_RequestGetError(mImpl->mResponse));
if (error.isValid())
{
hasError = true;
faultCode = error["faultCode"].asInt();
faultString = error["faultString"].asString();
}
else if (XMLRPC_ResponseIsFault(mImpl->mResponse))
{
hasFault = true;
faultCode = XMLRPC_GetResponseFaultCode(mImpl->mResponse);
faultString = XMLRPC_GetResponseFaultString(mImpl->mResponse);
}
if (hasError || hasFault)
{
mImpl->setStatus(LLXMLRPCTransaction::StatusXMLRPCError);
LL_WARNS() << "LLXMLRPCTransaction XMLRPC "
<< (hasError ? "error " : "fault ")
<< faultCode << ": "
<< faultString << LL_ENDL;
LL_WARNS() << "LLXMLRPCTransaction request URI: "
<< mImpl->mURI << LL_ENDL;
}
}
//=========================================================================
LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
XMLRPC_REQUEST request, bool useGzip, const LLSD& httpParams)
: mHttpRequest(),
mStatus(LLXMLRPCTransaction::StatusNotStarted),
mURI(uri),
mResponse(0)
{
init(request, useGzip, httpParams);
}
LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
const std::string& method, LLXMLRPCValue params, bool useGzip)
: mHttpRequest(),
mStatus(LLXMLRPCTransaction::StatusNotStarted),
mURI(uri),
mResponse(0)
{
XMLRPC_REQUEST request = XMLRPC_RequestNew();
XMLRPC_RequestSetMethodName(request, method.c_str());
XMLRPC_RequestSetRequestType(request, xmlrpc_request_call);
XMLRPC_RequestSetData(request, params.getValue());
init(request, useGzip, LLSD());
// DEV-28398: without this XMLRPC_RequestFree() call, it looks as though
// the 'request' object is simply leaked. It's less clear to me whether we
// should also ask to free request value data (second param 1), since the
// data come from 'params'.
XMLRPC_RequestFree(request, 1);
}
void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip, const LLSD& httpParams)
{
LLCore::HttpOptions::ptr_t httpOpts;
LLCore::HttpHeaders::ptr_t httpHeaders;
if (!mHttpRequest)
{
mHttpRequest = LLCore::HttpRequest::ptr_t(new LLCore::HttpRequest);
}
// LLRefCounted starts with a 1 ref, so don't add a ref in the smart pointer
httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions());
// delay between repeats will start from 5 sec and grow to 20 sec with each repeat
httpOpts->setMinBackoff(5E6L);
httpOpts->setMaxBackoff(20E6L);
httpOpts->setTimeout(httpParams.has("timeout") ? httpParams["timeout"].asInteger() : 40L);
if (httpParams.has("retries"))
{
httpOpts->setRetries(httpParams["retries"].asInteger());
}
if (httpParams.has("DNSCacheTimeout"))
{
httpOpts->setDNSCacheTimeout(httpParams["DNSCacheTimeout"].asInteger());
}
bool vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert");
mCertStore = gSavedSettings.getString("CertStore");
httpOpts->setSSLVerifyPeer( vefifySSLCert );
httpOpts->setSSLVerifyHost( vefifySSLCert );
// LLRefCounted starts with a 1 ref, so don't add a ref in the smart pointer
httpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders());
httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_TEXT_XML);
std::string user_agent = llformat("%s %d.%d.%d (%d)",
LLVersionInfo::instance().getChannel().c_str(),
LLVersionInfo::instance().getMajor(),
LLVersionInfo::instance().getMinor(),
LLVersionInfo::instance().getPatch(),
LLVersionInfo::instance().getBuild());
httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, user_agent);
///* Setting the DNS cache timeout to -1 disables it completely.
//This might help with bug #503 */
//httpOpts->setDNSCacheTimeout(-1);
LLCore::BufferArray::ptr_t body = LLCore::BufferArray::ptr_t(new LLCore::BufferArray());
// TODO: See if there is a way to serialize to a preallocated buffer I'm
// not fond of the copy here.
int requestSize(0);
char * requestText = XMLRPC_REQUEST_ToXML(request, &requestSize);
body->append(requestText, requestSize);
XMLRPC_Free(requestText);
mHandler = LLXMLRPCTransaction::Handler::ptr_t(new Handler( mHttpRequest, this ));
mPostH = mHttpRequest->requestPost(LLCore::HttpRequest::DEFAULT_POLICY_ID, 0,
mURI, body.get(), httpOpts, httpHeaders, mHandler);
}
LLXMLRPCTransaction::Impl::~Impl()
{
if (mResponse)
{
XMLRPC_RequestFree(mResponse, 1);
}
}
bool LLXMLRPCTransaction::Impl::process()
{
if (!mPostH || !mHttpRequest)
{
LL_WARNS() << "transaction failed." << LL_ENDL;
return true; //failed, quit.
}
switch (mStatus)
{
case LLXMLRPCTransaction::StatusComplete:
case LLXMLRPCTransaction::StatusCURLError:
case LLXMLRPCTransaction::StatusXMLRPCError:
case LLXMLRPCTransaction::StatusOtherError:
{
return true;
}
case LLXMLRPCTransaction::StatusNotStarted:
{
setStatus(LLXMLRPCTransaction::StatusStarted);
break;
}
default:
break;
}
LLCore::HttpStatus status = mHttpRequest->update(0);
status = mHttpRequest->getStatus();
if (!status)
{
return false;
}
return false;
}
void LLXMLRPCTransaction::Impl::setStatus(EStatus status,
const std::string& message, const std::string& uri)
{
mStatus = status;
mStatusMessage = message;
mStatusURI = uri;
if (mStatusMessage.empty())
{
switch (mStatus)
{
case StatusNotStarted:
mStatusMessage = "(not started)";
break;
case StatusStarted:
mStatusMessage = "(waiting for server response)";
break;
case StatusDownloading:
mStatusMessage = "(reading server response)";
break;
case StatusComplete:
mStatusMessage = "(done)";
break;
default:
// Usually this means that there's a problem with the login server,
// not with the client. Direct user to status page.
mStatusMessage = LLTrans::getString("server_is_down");
mStatusURI = "http://status.secondlifegrid.net/";
}
}
}
void LLXMLRPCTransaction::Impl::setHttpStatus(const LLCore::HttpStatus &status)
{
CURLcode code = static_cast<CURLcode>(status.toULong());
std::string message;
std::string uri = "http://support.secondlife.com";
LLURI failuri(mURI);
LLStringUtil::format_map_t args;
switch (code)
{
case CURLE_COULDNT_RESOLVE_HOST:
args["[HOSTNAME]"] = failuri.hostName();
message = LLTrans::getString("couldnt_resolve_host", args);
break;
case CURLE_SSL_PEER_CERTIFICATE:
message = LLTrans::getString("ssl_peer_certificate");
break;
case CURLE_SSL_CACERT:
case CURLE_SSL_CONNECT_ERROR:
message = LLTrans::getString("ssl_connect_error");
break;
default:
break;
}
mCurlCode = code;
setStatus(StatusCURLError, message, uri);
}
LLXMLRPCTransaction::LLXMLRPCTransaction(
const std::string& uri, XMLRPC_REQUEST request, bool useGzip, const LLSD& httpParams)
: impl(* new Impl(uri, request, useGzip, httpParams))
{ }
LLXMLRPCTransaction::LLXMLRPCTransaction(
const std::string& uri,
const std::string& method, LLXMLRPCValue params, bool useGzip)
: impl(* new Impl(uri, method, params, useGzip))
{ }
LLXMLRPCTransaction::~LLXMLRPCTransaction()
{
delete &impl;
}
bool LLXMLRPCTransaction::process()
{
return impl.process();
}
LLXMLRPCTransaction::EStatus LLXMLRPCTransaction::status(int* curlCode)
{
if (curlCode)
{
*curlCode =
(impl.mStatus == StatusCURLError)
? impl.mCurlCode
: CURLE_OK;
}
return impl.mStatus;
}
std::string LLXMLRPCTransaction::statusMessage()
{
return impl.mStatusMessage;
}
LLSD LLXMLRPCTransaction::getErrorCertData()
{
return impl.mErrorCertData;
}
std::string LLXMLRPCTransaction::statusURI()
{
return impl.mStatusURI;
}
XMLRPC_REQUEST LLXMLRPCTransaction::response()
{
return impl.mResponse;
}
LLXMLRPCValue LLXMLRPCTransaction::responseValue()
{
return LLXMLRPCValue(XMLRPC_RequestGetData(impl.mResponse));
}
F64 LLXMLRPCTransaction::transferRate()
{
if (impl.mStatus != StatusComplete)
{
return 0.0L;
}
double rate_bits_per_sec = impl.mTransferStats->mSpeedDownload * 8.0;
LL_INFOS("AppInit") << "Buffer size: " << impl.mResponseText.size() << " B" << LL_ENDL;
LL_DEBUGS("AppInit") << "Transfer size: " << impl.mTransferStats->mSizeDownload << " B" << LL_ENDL;
LL_DEBUGS("AppInit") << "Transfer time: " << impl.mTransferStats->mTotalTime << " s" << LL_ENDL;
LL_INFOS("AppInit") << "Transfer rate: " << rate_bits_per_sec / 1000.0 << " Kb/s" << LL_ENDL;
return rate_bits_per_sec;
}