PageRenderTime 370ms CodeModel.GetById 111ms app.highlight 154ms RepoModel.GetById 97ms app.codeStats 0ms

/indra/newview/llxmlrpclistener.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 537 lines | 375 code | 34 blank | 128 comment | 47 complexity | 10dbbf001130c14a78a9c81de75ae9f6 MD5 | raw file
  1/**
  2 * @file   llxmlrpclistener.cpp
  3 * @author Nat Goodspeed
  4 * @date   2009-03-18
  5 * @brief  Implementation for llxmlrpclistener.
  6 * 
  7 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
  8 * Second Life Viewer Source Code
  9 * Copyright (C) 2010, Linden Research, Inc.
 10 * 
 11 * This library is free software; you can redistribute it and/or
 12 * modify it under the terms of the GNU Lesser General Public
 13 * License as published by the Free Software Foundation;
 14 * version 2.1 of the License only.
 15 * 
 16 * This library is distributed in the hope that it will be useful,
 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 19 * Lesser General Public License for more details.
 20 * 
 21 * You should have received a copy of the GNU Lesser General Public
 22 * License along with this library; if not, write to the Free Software
 23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 24 * 
 25 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 26 * $/LicenseInfo$
 27 */
 28
 29
 30// Precompiled header
 31#include "llviewerprecompiledheaders.h"
 32// associated header
 33#include "llxmlrpclistener.h"
 34// STL headers
 35#include <map>
 36#include <set>
 37// std headers
 38// external library headers
 39#include <boost/scoped_ptr.hpp>
 40#include <boost/range.hpp>          // boost::begin(), boost::end()
 41#include <xmlrpc-epi/xmlrpc.h>
 42#include "curl/curl.h"
 43
 44// other Linden headers
 45#include "llerror.h"
 46#include "stringize.h"
 47#include "llxmlrpctransaction.h"
 48#include "llsecapi.h"
 49
 50#if LL_WINDOWS
 51#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
 52#endif
 53
 54template <typename STATUS>
 55class StatusMapperBase
 56{
 57    typedef std::map<STATUS, std::string> MapType;
 58
 59public:
 60    StatusMapperBase(const std::string& desc):
 61        mDesc(desc)
 62    {}
 63
 64    std::string lookup(STATUS status) const
 65    {
 66        typename MapType::const_iterator found = mMap.find(status);
 67        if (found != mMap.end())
 68        {
 69            return found->second;
 70        }
 71        return STRINGIZE("<unknown " << mDesc << " " << status << ">");
 72    }
 73
 74protected:
 75    std::string mDesc;
 76    MapType mMap;
 77};
 78
 79class StatusMapper: public StatusMapperBase<LLXMLRPCTransaction::EStatus>
 80{
 81public:
 82    StatusMapper(): StatusMapperBase<LLXMLRPCTransaction::EStatus>("Status")
 83    {
 84		mMap[LLXMLRPCTransaction::StatusNotStarted]  = "NotStarted";
 85		mMap[LLXMLRPCTransaction::StatusStarted]     = "Started";
 86		mMap[LLXMLRPCTransaction::StatusDownloading] = "Downloading";
 87		mMap[LLXMLRPCTransaction::StatusComplete]    = "Complete";
 88		mMap[LLXMLRPCTransaction::StatusCURLError]   = "CURLError";
 89		mMap[LLXMLRPCTransaction::StatusXMLRPCError] = "XMLRPCError";
 90		mMap[LLXMLRPCTransaction::StatusOtherError]  = "OtherError";
 91    }
 92};
 93
 94static const StatusMapper sStatusMapper;
 95
 96class CURLcodeMapper: public StatusMapperBase<CURLcode>
 97{
 98public:
 99    CURLcodeMapper(): StatusMapperBase<CURLcode>("CURLcode")
100    {
101        // from curl.h
102// skip the "CURLE_" prefix for each of these strings
103#define def(sym) (mMap[sym] = #sym + 6)
104        def(CURLE_OK);
105        def(CURLE_UNSUPPORTED_PROTOCOL);    /* 1 */
106        def(CURLE_FAILED_INIT);             /* 2 */
107        def(CURLE_URL_MALFORMAT);           /* 3 */
108        def(CURLE_URL_MALFORMAT_USER);      /* 4 - NOT USED */
109        def(CURLE_COULDNT_RESOLVE_PROXY);   /* 5 */
110        def(CURLE_COULDNT_RESOLVE_HOST);    /* 6 */
111        def(CURLE_COULDNT_CONNECT);         /* 7 */
112        def(CURLE_FTP_WEIRD_SERVER_REPLY);  /* 8 */
113        def(CURLE_FTP_ACCESS_DENIED);       /* 9 a service was denied by the FTP server
114                                          due to lack of access - when login fails
115                                          this is not returned. */
116        def(CURLE_FTP_USER_PASSWORD_INCORRECT); /* 10 - NOT USED */
117        def(CURLE_FTP_WEIRD_PASS_REPLY);    /* 11 */
118        def(CURLE_FTP_WEIRD_USER_REPLY);    /* 12 */
119        def(CURLE_FTP_WEIRD_PASV_REPLY);    /* 13 */
120        def(CURLE_FTP_WEIRD_227_FORMAT);    /* 14 */
121        def(CURLE_FTP_CANT_GET_HOST);       /* 15 */
122        def(CURLE_FTP_CANT_RECONNECT);      /* 16 */
123        def(CURLE_FTP_COULDNT_SET_BINARY);  /* 17 */
124        def(CURLE_PARTIAL_FILE);            /* 18 */
125        def(CURLE_FTP_COULDNT_RETR_FILE);   /* 19 */
126        def(CURLE_FTP_WRITE_ERROR);         /* 20 */
127        def(CURLE_FTP_QUOTE_ERROR);         /* 21 */
128        def(CURLE_HTTP_RETURNED_ERROR);     /* 22 */
129        def(CURLE_WRITE_ERROR);             /* 23 */
130        def(CURLE_MALFORMAT_USER);          /* 24 - NOT USED */
131        def(CURLE_UPLOAD_FAILED);           /* 25 - failed upload "command" */
132        def(CURLE_READ_ERROR);              /* 26 - could open/read from file */
133        def(CURLE_OUT_OF_MEMORY);           /* 27 */
134        /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error
135                 instead of a memory allocation error if CURL_DOES_CONVERSIONS
136                 is defined
137        */
138        def(CURLE_OPERATION_TIMEOUTED);     /* 28 - the timeout time was reached */
139        def(CURLE_FTP_COULDNT_SET_ASCII);   /* 29 - TYPE A failed */
140        def(CURLE_FTP_PORT_FAILED);         /* 30 - FTP PORT operation failed */
141        def(CURLE_FTP_COULDNT_USE_REST);    /* 31 - the REST command failed */
142        def(CURLE_FTP_COULDNT_GET_SIZE);    /* 32 - the SIZE command failed */
143        def(CURLE_HTTP_RANGE_ERROR);        /* 33 - RANGE "command" didn't work */
144        def(CURLE_HTTP_POST_ERROR);         /* 34 */
145        def(CURLE_SSL_CONNECT_ERROR);       /* 35 - wrong when connecting with SSL */
146        def(CURLE_BAD_DOWNLOAD_RESUME);     /* 36 - couldn't resume download */
147        def(CURLE_FILE_COULDNT_READ_FILE);  /* 37 */
148        def(CURLE_LDAP_CANNOT_BIND);        /* 38 */
149        def(CURLE_LDAP_SEARCH_FAILED);      /* 39 */
150        def(CURLE_LIBRARY_NOT_FOUND);       /* 40 */
151        def(CURLE_FUNCTION_NOT_FOUND);      /* 41 */
152        def(CURLE_ABORTED_BY_CALLBACK);     /* 42 */
153        def(CURLE_BAD_FUNCTION_ARGUMENT);   /* 43 */
154        def(CURLE_BAD_CALLING_ORDER);       /* 44 - NOT USED */
155        def(CURLE_INTERFACE_FAILED);        /* 45 - CURLOPT_INTERFACE failed */
156        def(CURLE_BAD_PASSWORD_ENTERED);    /* 46 - NOT USED */
157        def(CURLE_TOO_MANY_REDIRECTS );     /* 47 - catch endless re-direct loops */
158        def(CURLE_UNKNOWN_TELNET_OPTION);   /* 48 - User specified an unknown option */
159        def(CURLE_TELNET_OPTION_SYNTAX );   /* 49 - Malformed telnet option */
160        def(CURLE_OBSOLETE);                /* 50 - NOT USED */
161        def(CURLE_SSL_PEER_CERTIFICATE);    /* 51 - peer's certificate wasn't ok */
162        def(CURLE_GOT_NOTHING);             /* 52 - when this is a specific error */
163        def(CURLE_SSL_ENGINE_NOTFOUND);     /* 53 - SSL crypto engine not found */
164        def(CURLE_SSL_ENGINE_SETFAILED);    /* 54 - can not set SSL crypto engine as
165                                          default */
166        def(CURLE_SEND_ERROR);              /* 55 - failed sending network data */
167        def(CURLE_RECV_ERROR);              /* 56 - failure in receiving network data */
168        def(CURLE_SHARE_IN_USE);            /* 57 - share is in use */
169        def(CURLE_SSL_CERTPROBLEM);         /* 58 - problem with the local certificate */
170        def(CURLE_SSL_CIPHER);              /* 59 - couldn't use specified cipher */
171        def(CURLE_SSL_CACERT);              /* 60 - problem with the CA cert (path?) */
172        def(CURLE_BAD_CONTENT_ENCODING);    /* 61 - Unrecognized transfer encoding */
173        def(CURLE_LDAP_INVALID_URL);        /* 62 - Invalid LDAP URL */
174        def(CURLE_FILESIZE_EXCEEDED);       /* 63 - Maximum file size exceeded */
175        def(CURLE_FTP_SSL_FAILED);          /* 64 - Requested FTP SSL level failed */
176        def(CURLE_SEND_FAIL_REWIND);        /* 65 - Sending the data requires a rewind
177                                          that failed */
178        def(CURLE_SSL_ENGINE_INITFAILED);   /* 66 - failed to initialise ENGINE */
179        def(CURLE_LOGIN_DENIED);            /* 67 - user); password or similar was not
180                                          accepted and we failed to login */
181        def(CURLE_TFTP_NOTFOUND);           /* 68 - file not found on server */
182        def(CURLE_TFTP_PERM);               /* 69 - permission problem on server */
183        def(CURLE_TFTP_DISKFULL);           /* 70 - out of disk space on server */
184        def(CURLE_TFTP_ILLEGAL);            /* 71 - Illegal TFTP operation */
185        def(CURLE_TFTP_UNKNOWNID);          /* 72 - Unknown transfer ID */
186        def(CURLE_TFTP_EXISTS);             /* 73 - File already exists */
187        def(CURLE_TFTP_NOSUCHUSER);         /* 74 - No such user */
188        def(CURLE_CONV_FAILED);             /* 75 - conversion failed */
189        def(CURLE_CONV_REQD);               /* 76 - caller must register conversion
190                                          callbacks using curl_easy_setopt options
191                                          CURLOPT_CONV_FROM_NETWORK_FUNCTION);
192                                          CURLOPT_CONV_TO_NETWORK_FUNCTION); and
193                                          CURLOPT_CONV_FROM_UTF8_FUNCTION */
194        def(CURLE_SSL_CACERT_BADFILE);      /* 77 - could not load CACERT file); missing
195                                          or wrong format */
196        def(CURLE_REMOTE_FILE_NOT_FOUND);   /* 78 - remote file not found */
197        def(CURLE_SSH);                     /* 79 - error from the SSH layer); somewhat
198                                          generic so the error message will be of
199                                          interest when this has happened */
200
201        def(CURLE_SSL_SHUTDOWN_FAILED);     /* 80 - Failed to shut down the SSL
202                                          connection */
203#undef  def
204    }
205};
206
207static const CURLcodeMapper sCURLcodeMapper;
208
209LLXMLRPCListener::LLXMLRPCListener(const std::string& pumpname):
210    mBoundListener(LLEventPumps::instance().
211                   obtain(pumpname).
212                   listen("LLXMLRPCListener", boost::bind(&LLXMLRPCListener::process, this, _1)))
213{
214}
215
216/**
217 * Capture an outstanding LLXMLRPCTransaction and poll it periodically until
218 * done.
219 *
220 * The sequence is:
221 * # Instantiate Poller, which instantiates, populates and initiates an
222 *   LLXMLRPCTransaction. Poller self-registers on the LLEventPump named
223 *   "mainloop".
224 * # "mainloop" is conventionally pumped once per frame. On each such call,
225 *   Poller checks its LLXMLRPCTransaction for completion.
226 * # When the LLXMLRPCTransaction completes, Poller collects results (if any)
227 *   and sends notification.
228 * # The tricky part: Poller frees itself (and thus its LLXMLRPCTransaction)
229 *   when done. The only external reference to it is the connection to the
230 *   "mainloop" LLEventPump.
231 */
232class Poller
233{
234public:
235    /// Validate the passed request for required fields, then use it to
236    /// populate an XMLRPC_REQUEST and an associated LLXMLRPCTransaction. Send
237    /// the request.
238    Poller(const LLSD& command):
239        mReqID(command),
240        mUri(command["uri"]),
241        mMethod(command["method"]),
242        mReplyPump(command["reply"])
243    {
244        // LL_ERRS if any of these are missing
245        const char* required[] = { "uri", "method", "reply" };
246        // optional: "options" (array of string)
247        // Validate the request
248        std::set<std::string> missing;
249        for (const char** ri = boost::begin(required); ri != boost::end(required); ++ri)
250        {
251            // If the command does not contain this required entry, add it to 'missing'.
252            if (! command.has(*ri))
253            {
254                missing.insert(*ri);
255            }
256        }
257        if (! missing.empty())
258        {
259            LL_ERRS("LLXMLRPCListener") << mMethod << " request missing params: ";
260            const char* separator = "";
261            for (std::set<std::string>::const_iterator mi(missing.begin()), mend(missing.end());
262                 mi != mend; ++mi)
263            {
264                LL_CONT << separator << *mi;
265                separator = ", ";
266            }
267            LL_CONT << LL_ENDL;
268        }
269
270        // Build the XMLRPC request.
271        XMLRPC_REQUEST request = XMLRPC_RequestNew();
272        XMLRPC_RequestSetMethodName(request, mMethod.c_str());
273        XMLRPC_RequestSetRequestType(request, xmlrpc_request_call);
274        XMLRPC_VALUE xparams = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
275        LLSD params(command["params"]);
276        if (params.isMap())
277        {
278            for (LLSD::map_const_iterator pi(params.beginMap()), pend(params.endMap());
279                 pi != pend; ++pi)
280            {
281                std::string name(pi->first);
282                LLSD param(pi->second);
283                if (param.isString())
284                {
285                    XMLRPC_VectorAppendString(xparams, name.c_str(), param.asString().c_str(), 0);
286                }
287                else if (param.isInteger() || param.isBoolean())
288                {
289                    XMLRPC_VectorAppendInt(xparams, name.c_str(), param.asInteger());
290                }
291                else if (param.isReal())
292                {
293                    XMLRPC_VectorAppendDouble(xparams, name.c_str(), param.asReal());
294                }
295                else
296                {
297                    LL_ERRS("LLXMLRPCListener") << mMethod << " request param "
298                                                << name << " has unknown type: " << param << LL_ENDL;
299                }
300            }
301        }
302        LLSD options(command["options"]);
303        if (options.isArray())
304        {
305            XMLRPC_VALUE xoptions = XMLRPC_CreateVector("options", xmlrpc_vector_array);
306            for (LLSD::array_const_iterator oi(options.beginArray()), oend(options.endArray());
307                 oi != oend; ++oi)
308            {
309                XMLRPC_VectorAppendString(xoptions, NULL, oi->asString().c_str(), 0);
310            }
311            XMLRPC_AddValueToVector(xparams, xoptions);
312        }
313        XMLRPC_RequestSetData(request, xparams);
314
315        mTransaction.reset(new LLXMLRPCTransaction(mUri, request));
316		mPreviousStatus = mTransaction->status(NULL);
317
318        // Free the XMLRPC_REQUEST object and the attached data values.
319        XMLRPC_RequestFree(request, 1);
320
321        // Now ensure that we get regular callbacks to poll for completion.
322        mBoundListener =
323            LLEventPumps::instance().
324            obtain("mainloop").
325            listen(LLEventPump::inventName(), boost::bind(&Poller::poll, this, _1));
326
327        LL_INFOS("LLXMLRPCListener") << mMethod << " request sent to " << mUri << LL_ENDL;
328    }
329
330    /// called by "mainloop" LLEventPump
331    bool poll(const LLSD&)
332    {
333        bool done = mTransaction->process();
334
335        CURLcode curlcode;
336        LLXMLRPCTransaction::EStatus status;
337        {
338            // LLXMLRPCTransaction::status() is defined to accept int* rather
339            // than CURLcode*. I don't feel the urge to fix the signature, but
340            // we want a CURLcode rather than an int. So fetch it as a local
341            // int, but then assign to a CURLcode for the remainder of this
342            // method.
343            int curlint;
344            status = mTransaction->status(&curlint);
345            curlcode = CURLcode(curlint);
346        }
347
348        LLSD data(mReqID.makeResponse());
349        data["status"] = sStatusMapper.lookup(status);
350        data["errorcode"] = sCURLcodeMapper.lookup(curlcode);
351        data["error"] = "";
352        data["transfer_rate"] = 0.0;
353        LLEventPump& replyPump(LLEventPumps::instance().obtain(mReplyPump));
354		if (! done)
355        {
356            // Not done yet, carry on.
357			if (status == LLXMLRPCTransaction::StatusDownloading
358				&& status != mPreviousStatus)
359			{
360				// If a response has been received, send the 
361				// 'downloading' status if it hasn't been sent.
362				replyPump.post(data);
363			}
364
365			mPreviousStatus = status;
366            return false;
367        }
368
369        // Here the transaction is complete. Check status.
370        data["error"] = mTransaction->statusMessage();
371		data["transfer_rate"] = mTransaction->transferRate();
372        LL_INFOS("LLXMLRPCListener") << mMethod << " result from " << mUri << ": status "
373                                     << data["status"].asString() << ", errorcode "
374                                     << data["errorcode"].asString()
375                                     << " (" << data["error"].asString() << ")"
376                                     << LL_ENDL;
377		
378		switch (curlcode)
379		{
380			case CURLE_SSL_PEER_CERTIFICATE:
381			case CURLE_SSL_CACERT:
382			{
383				LLPointer<LLCertificate> error_cert(mTransaction->getErrorCert());
384				if(error_cert)
385				{
386					data["certificate"] = error_cert->getPem();
387				}
388				break;
389			}
390			default:
391				break;
392		}
393        // values of 'curlcode':
394        // CURLE_COULDNT_RESOLVE_HOST,
395        // CURLE_SSL_PEER_CERTIFICATE,
396        // CURLE_SSL_CACERT,
397        // CURLE_SSL_CONNECT_ERROR.
398        // Given 'message', need we care?
399        if (status == LLXMLRPCTransaction::StatusComplete)
400        {
401            // Success! Parse data.
402            std::string status_string(data["status"]);
403            data["responses"] = parseResponse(status_string);
404            data["status"] = status_string;
405        }
406
407        // whether successful or not, send reply on requested LLEventPump
408        replyPump.post(data);
409
410        // Because mTransaction is a boost::scoped_ptr, deleting this object
411        // frees our LLXMLRPCTransaction object.
412        // Because mBoundListener is an LLTempBoundListener, deleting this
413        // object disconnects it from "mainloop".
414        // *** MUST BE LAST ***
415        delete this;
416        return false;
417    }
418
419private:
420    /// Derived from LLUserAuth::parseResponse() and parseOptionInto()
421    LLSD parseResponse(std::string& status_string)
422    {
423        // Extract every member into data["responses"] (a map of string
424        // values).
425        XMLRPC_REQUEST response = mTransaction->response();
426        if (! response)
427        {
428            LL_DEBUGS("LLXMLRPCListener") << "No response" << LL_ENDL;
429            return LLSD();
430        }
431
432        XMLRPC_VALUE param = XMLRPC_RequestGetData(response);
433        if (! param)
434        {
435            LL_DEBUGS("LLXMLRPCListener") << "Response contains no data" << LL_ENDL;
436            return LLSD();
437        }
438
439        // Now, parse everything
440        return parseValues(status_string, "", param);
441    }
442
443    /**
444     * Parse key/value pairs from a given XMLRPC_VALUE into an LLSD map.
445     * @param key_pfx Used to describe a given key in log messages. At top
446     * level, pass "". When parsing an options array, pass the top-level key
447     * name of the array plus the index of the array entry; to this we'll
448     * append the subkey of interest.
449     * @param param XMLRPC_VALUE iterator. At top level, pass
450     * XMLRPC_RequestGetData(XMLRPC_REQUEST).
451     */
452    LLSD parseValues(std::string& status_string, const std::string& key_pfx, XMLRPC_VALUE param)
453    {
454        LLSD responses;
455        for (XMLRPC_VALUE current = XMLRPC_VectorRewind(param); current;
456             current = XMLRPC_VectorNext(param))
457        {
458            std::string key(XMLRPC_GetValueID(current));
459            LL_DEBUGS("LLXMLRPCListener") << "key: " << key_pfx << key << LL_ENDL;
460            XMLRPC_VALUE_TYPE_EASY type = XMLRPC_GetValueTypeEasy(current);
461            if (xmlrpc_type_string == type)
462            {
463                LLSD::String val(XMLRPC_GetValueString(current));
464                LL_DEBUGS("LLXMLRPCListener") << "val: " << val << LL_ENDL;
465                responses.insert(key, val);
466            }
467            else if (xmlrpc_type_int == type)
468            {
469                LLSD::Integer val(XMLRPC_GetValueInt(current));
470                LL_DEBUGS("LLXMLRPCListener") << "val: " << val << LL_ENDL;
471                responses.insert(key, val);
472            }
473            else if (xmlrpc_type_double == type)
474            {
475                LLSD::Real val(XMLRPC_GetValueDouble(current));
476                LL_DEBUGS("LLXMLRPCListener") << "val: " << val << LL_ENDL;
477                responses.insert(key, val);
478            }
479            else if (xmlrpc_type_array == type)
480            {
481                // We expect this to be an array of submaps. Walk the array,
482                // recursively parsing each submap and collecting them.
483                LLSD array;
484                int i = 0;          // for descriptive purposes
485                for (XMLRPC_VALUE row = XMLRPC_VectorRewind(current); row;
486                     row = XMLRPC_VectorNext(current), ++i)
487                {
488                    // Recursive call. For the lower-level key_pfx, if 'key'
489                    // is "foo", pass "foo[0]:", then "foo[1]:", etc. In the
490                    // nested call, a subkey "bar" will then be logged as
491                    // "foo[0]:bar", and so forth.
492                    // Parse the scalar subkey/value pairs from this array
493                    // entry into a temp submap. Collect such submaps in 'array'.
494                    array.append(parseValues(status_string,
495                                             STRINGIZE(key_pfx << key << '[' << i << "]:"),
496                                             row));
497                }
498                // Having collected an 'array' of 'submap's, insert that whole
499                // 'array' as the value of this 'key'.
500                responses.insert(key, array);
501            }
502            else if (xmlrpc_type_struct == type)
503            {
504                LLSD submap = parseValues(status_string,
505                                          STRINGIZE(key_pfx << key << ':'),
506                                          current);
507                responses.insert(key, submap);
508            }
509            else
510            {
511                // whoops - unrecognized type
512                LL_WARNS("LLXMLRPCListener") << "Unhandled xmlrpc type " << type << " for key "
513                                             << key_pfx << key << LL_ENDL;
514                responses.insert(key, STRINGIZE("<bad XMLRPC type " << type << '>'));
515                status_string = "BadType";
516            }
517        }
518        return responses;
519    }
520
521    const LLReqID mReqID;
522    const std::string mUri;
523    const std::string mMethod;
524    const std::string mReplyPump;
525    LLTempBoundListener mBoundListener;
526    boost::scoped_ptr<LLXMLRPCTransaction> mTransaction;
527	LLXMLRPCTransaction::EStatus mPreviousStatus; // To detect state changes.
528};
529
530bool LLXMLRPCListener::process(const LLSD& command)
531{
532    // Allocate a new heap Poller, but do not save a pointer to it. Poller
533    // will check its own status and free itself on completion of the request.
534    (new Poller(command));
535    // conventional event listener return
536    return false;
537}