/**
* @file llfiltersd2xmlrpc.cpp
* @author Phoenix
* @date 2005-04-26
*
* $LicenseInfo:firstyear=2005&license=viewergpl$
*
* Copyright (c) 2005-2007, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*/
/**
* xml rpc request:
*
*
* examples.getStateName
* 41
*
*
*
* xml rpc response:
*
*
*
* South Dakota
*
*
*
* xml rpc fault:
*
*
*
*
* faultCode4
* faultString...
*
*
*
*
* llsd rpc request:
*
* { 'method':'...', 'parameter':...]}
*
*
* llsd rpc response:
*
* { 'response':... }
*
*
* llsd rpc fault:
*
* { 'fault': {'code':i..., 'description':'...'} }
*
*
*/
#include "linden_common.h"
#include "llfiltersd2xmlrpc.h"
#include
#include
#include
#include "apr-1/apr_base64.h"
#include "llbuffer.h"
#include "llbufferstream.h"
#include "llmemorystream.h"
#include "llsd.h"
#include "llsdserialize.h"
#include "lluuid.h"
// spammy mode
//#define LL_SPEW_STREAM_OUT_DEBUGGING 1
/**
* String constants
*/
static const char XML_HEADER[] = "";
static const char XMLRPC_REQUEST_HEADER_1[] = "";
static const char XMLRPC_REQUEST_HEADER_2[] = "";
static const char XMLRPC_REQUEST_FOOTER[] = "";
static const char XMLRPC_METHOD_RESPONSE_HEADER[] = "";
static const char XMLRPC_METHOD_RESPONSE_FOOTER[] = "";
static const char XMLRPC_RESPONSE_HEADER[] = "";
static const char XMLRPC_RESPONSE_FOOTER[] = "";
static const char XMLRPC_FAULT_1[] = "faultCode";
static const char XMLRPC_FAULT_2[] = "faultString";
static const char XMLRPC_FAULT_3[] = "";
static const char LLSDRPC_RESPONSE_HEADER[] = "{'response':";
static const char LLSDRPC_RESPONSE_FOOTER[] = "}";
const char LLSDRPC_REQUEST_HEADER_1[] = "{'method':'";
const char LLSDRPC_REQUEST_HEADER_2[] = "', 'parameter': ";
const char LLSDRPC_REQUEST_FOOTER[] = "}";
static const char LLSDRPC_FAULT_HADER_1[] = "{ 'fault': {'code':i";
static const char LLSDRPC_FAULT_HADER_2[] = ", 'description':";
static const char LLSDRPC_FAULT_FOOTER[] = "} }";
static const S32 DEFAULT_PRECISION = 20;
/**
* LLFilterSD2XMLRPC
*/
LLFilterSD2XMLRPC::LLFilterSD2XMLRPC()
{
}
LLFilterSD2XMLRPC::~LLFilterSD2XMLRPC()
{
}
std::string xml_escape_string(const std::string& in)
{
std::ostringstream out;
std::string::const_iterator it = in.begin();
std::string::const_iterator end = in.end();
for(; it != end; ++it)
{
switch((*it))
{
case '<':
out << "<";
break;
case '>':
out << ">";
break;
case '&':
out << "&";
break;
case '\'':
out << "'";
break;
case '"':
out << """;
break;
default:
out << (*it);
break;
}
}
return out.str();
}
void LLFilterSD2XMLRPC::streamOut(std::ostream& ostr, const LLSD& sd)
{
ostr << "";
switch(sd.type())
{
case LLSD::TypeMap:
{
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(map) BEGIN" << llendl;
#endif
ostr << "";
if(ostr.fail())
{
llinfos << "STREAM FAILURE writing struct" << llendl;
}
LLSD::map_const_iterator it = sd.beginMap();
LLSD::map_const_iterator end = sd.endMap();
for(; it != end; ++it)
{
ostr << "" << xml_escape_string((*it).first)
<< "";
streamOut(ostr, (*it).second);
if(ostr.fail())
{
llinfos << "STREAM FAILURE writing '" << (*it).first
<< "' with sd type " << (*it).second.type() << llendl;
}
ostr << "";
}
ostr << "";
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(map) END" << llendl;
#endif
break;
}
case LLSD::TypeArray:
{
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(array) BEGIN" << llendl;
#endif
ostr << "";
LLSD::array_const_iterator it = sd.beginArray();
LLSD::array_const_iterator end = sd.endArray();
for(; it != end; ++it)
{
streamOut(ostr, *it);
if(ostr.fail())
{
llinfos << "STREAM FAILURE writing array element sd type "
<< (*it).type() << llendl;
}
}
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(array) END" << llendl;
#endif
ostr << "";
break;
}
case LLSD::TypeUndefined:
// treat undefined as a bool with a false value.
case LLSD::TypeBoolean:
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(bool)" << llendl;
#endif
ostr << "" << (sd.asBoolean() ? "1" : "0") << "";
break;
case LLSD::TypeInteger:
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(int)" << llendl;
#endif
ostr << "" << sd.asInteger() << "";
break;
case LLSD::TypeReal:
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(real)" << llendl;
#endif
ostr << "" << sd.asReal() << "";
break;
case LLSD::TypeString:
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(string)" << llendl;
#endif
ostr << "" << xml_escape_string(sd.asString()) << "";
break;
case LLSD::TypeUUID:
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(uuid)" << llendl;
#endif
// serialize it as a string
ostr << "" << sd.asString() << "";
break;
case LLSD::TypeURI:
{
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(uri)" << llendl;
#endif
// serialize it as a string
ostr << "" << xml_escape_string(sd.asString()) << "";
break;
}
case LLSD::TypeBinary:
{
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(binary)" << llendl;
#endif
// this is pretty inefficient, but we'll deal with that
// problem when it becomes one.
ostr << "";
LLSD::Binary buffer = sd.asBinary();
if(!buffer.empty())
{
// *TODO: convert to LLBase64
int b64_buffer_length = apr_base64_encode_len(buffer.size());
char* b64_buffer = new char[b64_buffer_length];
b64_buffer_length = apr_base64_encode_binary(
b64_buffer,
&buffer[0],
buffer.size());
ostr.write(b64_buffer, b64_buffer_length - 1);
delete[] b64_buffer;
}
ostr << "";
break;
}
case LLSD::TypeDate:
#if LL_SPEW_STREAM_OUT_DEBUGGING
llinfos << "streamOut(date)" << llendl;
#endif
// no need to escape this since it will be alpha-numeric.
ostr << "" << sd.asString() << "";
break;
default:
// unhandled type
llwarns << "Unhandled structured data type: " << sd.type()
<< llendl;
break;
}
ostr << "";
}
/**
* LLFilterSD2XMLRPCResponse
*/
LLFilterSD2XMLRPCResponse::LLFilterSD2XMLRPCResponse()
{
}
LLFilterSD2XMLRPCResponse::~LLFilterSD2XMLRPCResponse()
{
}
// virtual
LLIOPipe::EStatus LLFilterSD2XMLRPCResponse::process_impl(
const LLChannelDescriptors& channels,
buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump)
{
PUMP_DEBUG;
// This pipe does not work if it does not have everyting. This
// could be addressed by making a stream parser for llsd which
// handled partial information.
if(!eos)
{
return STATUS_BREAK;
}
PUMP_DEBUG;
// we have everyting in the buffer, so turn the structure data rpc
// response into an xml rpc response.
LLBufferStream stream(channels, buffer.get());
stream << XML_HEADER << XMLRPC_METHOD_RESPONSE_HEADER;
LLSD sd;
LLSDSerialize::fromNotation(sd, stream, buffer->count(channels.in()));
PUMP_DEBUG;
LLIOPipe::EStatus rv = STATUS_ERROR;
if(sd.has("response"))
{
PUMP_DEBUG;
// it is a normal response. pack it up and ship it out.
stream.precision(DEFAULT_PRECISION);
stream << XMLRPC_RESPONSE_HEADER;
streamOut(stream, sd["response"]);
stream << XMLRPC_RESPONSE_FOOTER << XMLRPC_METHOD_RESPONSE_FOOTER;
rv = STATUS_DONE;
}
else if(sd.has("fault"))
{
PUMP_DEBUG;
// it is a fault.
stream << XMLRPC_FAULT_1 << sd["fault"]["code"].asInteger()
<< XMLRPC_FAULT_2
<< xml_escape_string(sd["fault"]["description"].asString())
<< XMLRPC_FAULT_3 << XMLRPC_METHOD_RESPONSE_FOOTER;
rv = STATUS_DONE;
}
else
{
llwarns << "Unable to determine the type of LLSD response." << llendl;
}
PUMP_DEBUG;
return rv;
}
/**
* LLFilterSD2XMLRPCRequest
*/
LLFilterSD2XMLRPCRequest::LLFilterSD2XMLRPCRequest()
{
}
LLFilterSD2XMLRPCRequest::LLFilterSD2XMLRPCRequest(const char* method)
{
if(method)
{
mMethod.assign(method);
}
}
LLFilterSD2XMLRPCRequest::~LLFilterSD2XMLRPCRequest()
{
}
// virtual
LLIOPipe::EStatus LLFilterSD2XMLRPCRequest::process_impl(
const LLChannelDescriptors& channels,
buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump)
{
// This pipe does not work if it does not have everyting. This
// could be addressed by making a stream parser for llsd which
// handled partial information.
PUMP_DEBUG;
if(!eos)
{
llinfos << "!eos" << llendl;
return STATUS_BREAK;
}
// See if we can parse it
LLBufferStream stream(channels, buffer.get());
LLSD sd;
LLSDSerialize::fromNotation(sd, stream, buffer->count(channels.in()));
if(stream.fail())
{
llinfos << "STREAM FAILURE reading structure data." << llendl;
}
PUMP_DEBUG;
// We can get the method and parameters from either the member
// function or passed in via the buffer. We prefer the buffer if
// we found a parameter and a method, or fall back to using
// mMethod and putting everyting in the buffer into the parameter.
std::string method;
LLSD param_sd;
if(sd.has("method") && sd.has("parameter"))
{
method = sd["method"].asString();
param_sd = sd["parameter"];
}
else
{
method = mMethod;
param_sd = sd;
}
if(method.empty())
{
llwarns << "SD -> XML Request no method found." << llendl;
return STATUS_ERROR;
}
PUMP_DEBUG;
// We have a method, and some kind of parameter, so package it up
// and send it out.
LLBufferStream ostream(channels, buffer.get());
ostream.precision(DEFAULT_PRECISION);
if(ostream.fail())
{
llinfos << "STREAM FAILURE setting precision" << llendl;
}
ostream << XML_HEADER << XMLRPC_REQUEST_HEADER_1
<< xml_escape_string(method) << XMLRPC_REQUEST_HEADER_2;
if(ostream.fail())
{
llinfos << "STREAM FAILURE writing method headers" << llendl;
}
switch(param_sd.type())
{
case LLSD::TypeMap:
// If the params are a map, then we do not want to iterate
// through them since the iterators returned will be map
// ordered un-named values, which will lose the names, and
// only stream the values, turning it into an array.
ostream << "";
streamOut(ostream, param_sd);
ostream << "";
break;
case LLSD::TypeArray:
{
LLSD::array_iterator it = param_sd.beginArray();
LLSD::array_iterator end = param_sd.endArray();
for(; it != end; ++it)
{
ostream << "";
streamOut(ostream, *it);
ostream << "";
}
break;
}
default:
ostream << "";
streamOut(ostream, param_sd);
ostream << "";
break;
}
stream << XMLRPC_REQUEST_FOOTER;
return STATUS_DONE;
}
/**
* LLFilterXMLRPCResponse2LLSD
*/
// this is a c function here since it's really an implementation
// detail that requires a header file just get the definition of the
// parameters.
LLIOPipe::EStatus stream_out(std::ostream& ostr, XMLRPC_VALUE value)
{
XMLRPC_VALUE_TYPE_EASY type = XMLRPC_GetValueTypeEasy(value);
LLIOPipe::EStatus status = LLIOPipe::STATUS_OK;
switch(type)
{
case xmlrpc_type_base64:
{
S32 len = XMLRPC_GetValueStringLen(value);
const char* buf = XMLRPC_GetValueBase64(value);
ostr << " b(";
if((len > 0) && buf)
{
ostr << len << ")\"";
ostr.write(buf, len);
ostr << "\"";
}
else
{
ostr << "0)\"\"";
}
break;
}
case xmlrpc_type_boolean:
//lldebugs << "stream_out() bool" << llendl;
ostr << " " << (XMLRPC_GetValueBoolean(value) ? "true" : "false");
break;
case xmlrpc_type_datetime:
ostr << " d\"" << XMLRPC_GetValueDateTime_ISO8601(value) << "\"";
break;
case xmlrpc_type_double:
ostr << " r" << XMLRPC_GetValueDouble(value);
//lldebugs << "stream_out() double" << XMLRPC_GetValueDouble(value)
// << llendl;
break;
case xmlrpc_type_int:
ostr << " i" << XMLRPC_GetValueInt(value);
//lldebugs << "stream_out() integer:" << XMLRPC_GetValueInt(value)
// << llendl;
break;
case xmlrpc_type_string:
//lldebugs << "stream_out() string: " << str << llendl;
ostr << " s(" << XMLRPC_GetValueStringLen(value) << ")'"
<< XMLRPC_GetValueString(value) << "'";
break;
case xmlrpc_type_array: // vector
case xmlrpc_type_mixed: // vector
{
//lldebugs << "stream_out() array" << llendl;
ostr << " [";
U32 needs_comma = 0;
XMLRPC_VALUE current = XMLRPC_VectorRewind(value);
while(current && (LLIOPipe::STATUS_OK == status))
{
if(needs_comma++) ostr << ",";
status = stream_out(ostr, current);
current = XMLRPC_VectorNext(value);
}
ostr << "]";
break;
}
case xmlrpc_type_struct: // still vector
{
//lldebugs << "stream_out() struct" << llendl;
ostr << " {";
std::string name;
U32 needs_comma = 0;
XMLRPC_VALUE current = XMLRPC_VectorRewind(value);
while(current && (LLIOPipe::STATUS_OK == status))
{
if(needs_comma++) ostr << ",";
name.assign(XMLRPC_GetValueID(current));
ostr << "'" << LLSDNotationFormatter::escapeString(name) << "':";
status = stream_out(ostr, current);
current = XMLRPC_VectorNext(value);
}
ostr << "}";
break;
}
case xmlrpc_type_empty:
case xmlrpc_type_none:
default:
status = LLIOPipe::STATUS_ERROR;
llwarns << "Found an empty xmlrpc type.." << llendl;
// not much we can do here...
break;
};
return status;
}
LLFilterXMLRPCResponse2LLSD::LLFilterXMLRPCResponse2LLSD()
{
}
LLFilterXMLRPCResponse2LLSD::~LLFilterXMLRPCResponse2LLSD()
{
}
LLIOPipe::EStatus LLFilterXMLRPCResponse2LLSD::process_impl(
const LLChannelDescriptors& channels,
buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump)
{
PUMP_DEBUG;
if(!eos) return STATUS_BREAK;
if(!buffer) return STATUS_ERROR;
PUMP_DEBUG;
// *FIX: This technique for reading data is far from optimal. We
// need to have some kind of istream interface into the xml
// parser...
S32 bytes = buffer->countAfter(channels.in(), NULL);
if(!bytes) return STATUS_ERROR;
char* buf = new char[bytes + 1];
buf[bytes] = '\0';
buffer->readAfter(channels.in(), NULL, (U8*)buf, bytes);
//lldebugs << "xmlrpc response: " << buf << llendl;
PUMP_DEBUG;
XMLRPC_REQUEST response = XMLRPC_REQUEST_FromXML(
buf,
bytes,
NULL);
if(!response)
{
llwarns << "XML -> SD Response unable to parse xml." << llendl;
delete[] buf;
return STATUS_ERROR;
}
PUMP_DEBUG;
LLBufferStream stream(channels, buffer.get());
stream.precision(DEFAULT_PRECISION);
if(XMLRPC_ResponseIsFault(response))
{
PUMP_DEBUG;
stream << LLSDRPC_FAULT_HADER_1
<< XMLRPC_GetResponseFaultCode(response)
<< LLSDRPC_FAULT_HADER_2;
const char* fault_str = XMLRPC_GetResponseFaultString(response);
std::string fault_string;
if(fault_str)
{
fault_string.assign(fault_str);
}
stream << "'" << LLSDNotationFormatter::escapeString(fault_string)
<< "'" <countAfter(channels.in(), NULL);
if(!bytes) return STATUS_ERROR;
char* buf = new char[bytes + 1];
buf[bytes] = '\0';
buffer->readAfter(channels.in(), NULL, (U8*)buf, bytes);
//lldebugs << "xmlrpc request: " << buf << llendl;
PUMP_DEBUG;
XMLRPC_REQUEST request = XMLRPC_REQUEST_FromXML(
buf,
bytes,
NULL);
if(!request)
{
llwarns << "XML -> SD Request process parse error." << llendl;
delete[] buf;
return STATUS_ERROR;
}
PUMP_DEBUG;
LLBufferStream stream(channels, buffer.get());
stream.precision(DEFAULT_PRECISION);
const char* name = XMLRPC_RequestGetMethodName(request);
stream << LLSDRPC_REQUEST_HEADER_1 << (name ? name : "")
<< LLSDRPC_REQUEST_HEADER_2;
XMLRPC_VALUE param = XMLRPC_RequestGetData(request);
if(param)
{
PUMP_DEBUG;
S32 size = XMLRPC_VectorSize(param);
if(size > 1)
{
// if there are multiple parameters, stuff the values into
// an array so that the next step in the chain can read them.
stream << "[";
}
XMLRPC_VALUE current = XMLRPC_VectorRewind(param);
bool needs_comma = false;
while(current)
{
if(needs_comma)
{
stream << ",";
}
needs_comma = true;
stream_out(stream, current);
current = XMLRPC_VectorNext(param);
}
if(size > 1)
{
// close the array
stream << "]";
}
}
stream << LLSDRPC_REQUEST_FOOTER;
XMLRPC_RequestFree(request, 1);
delete[] buf;
PUMP_DEBUG;
return STATUS_DONE;
}