From 6df2755ba6b24d0cefd52ce175b0212dd46c9b10 Mon Sep 17 00:00:00 2001
From: Aaron Brashears <aaronb@lindenlab.com>
Date: Mon, 18 May 2009 23:38:35 +0000
Subject: Result of svn merge -r119432:120464
 svn+ssh://svn/svn/linden/branches/http_database/merge-03 into trunk. QAR-1462

---
 indra/lib/python/indra/base/cllsd_test.py    |   2 +-
 indra/lib/python/indra/base/llsd.py          |  22 +++--
 indra/lib/python/indra/base/metrics.py       |  94 +++++++++++++++---
 indra/lib/python/indra/ipc/servicebuilder.py |  22 ++++-
 indra/lib/python/indra/ipc/siesta.py         | 132 ++++++++++++++++++-------
 indra/lib/python/indra/util/named_query.py   |  21 ++--
 indra/llcommon/CMakeLists.txt                |   2 -
 indra/llcommon/llapp.cpp                     |  32 ++++--
 indra/llcommon/llapp.h                       |  45 +++++++--
 indra/llcommon/llerror.cpp                   |   7 +-
 indra/llcommon/llliveappconfig.cpp           |  32 ++++--
 indra/llcommon/llliveappconfig.h             |  34 +++++--
 indra/llcommon/lllivefile.cpp                |  35 +++++--
 indra/llcommon/lllivefile.h                  |  50 ++++++++--
 indra/llcommon/llstat.cpp                    |  11 ++-
 indra/llcommon/llstring.h                    |  46 ++++++++-
 indra/llcommon/lluri.cpp                     |   5 +-
 indra/llcommon/lluri.h                       |  17 +---
 indra/llcrashlogger/llcrashlogger.cpp        |   3 +-
 indra/llinventory/llparcel.cpp               |   2 +-
 indra/llinventory/llparcel.h                 |   8 --
 indra/llmessage/llhttpclient.cpp             | 140 ++++++++++++++++++++-------
 indra/llmessage/llhttpclient.h               |   8 ++
 indra/llmessage/llmessageconfig.cpp          |   7 +-
 indra/llmessage/llurlrequest.cpp             |  20 ++++
 indra/llmessage/llurlrequest.h               |   5 +
 indra/llmessage/message.h                    |  16 +++
 indra/newview/llappviewer.cpp                |   1 +
 28 files changed, 632 insertions(+), 187 deletions(-)

(limited to 'indra')

diff --git a/indra/lib/python/indra/base/cllsd_test.py b/indra/lib/python/indra/base/cllsd_test.py
index 3af59e741a..0b20d99d80 100644
--- a/indra/lib/python/indra/base/cllsd_test.py
+++ b/indra/lib/python/indra/base/cllsd_test.py
@@ -10,7 +10,7 @@ values = (
     '&<>',
     u'\u81acj',
     llsd.uri('http://foo<'),
-    lluuid.LLUUID(),
+    lluuid.UUID(),
     llsd.LLSD(['thing']),
     1,
     myint(31337),
diff --git a/indra/lib/python/indra/base/llsd.py b/indra/lib/python/indra/base/llsd.py
index 9534d5935e..1190d88663 100644
--- a/indra/lib/python/indra/base/llsd.py
+++ b/indra/lib/python/indra/base/llsd.py
@@ -72,8 +72,11 @@ BOOL_FALSE = ('0', '0.0', 'false', '')
 
 
 def format_datestr(v):
-    """ Formats a datetime object into the string format shared by xml and notation serializations."""
-    return v.isoformat() + 'Z'
+    """ Formats a datetime or date object into the string format shared by xml and notation serializations."""
+    if hasattr(v, 'microsecond'):
+        return v.isoformat() + 'Z'
+    else:
+        return v.strftime('%Y-%m-%dT%H:%M:%SZ')
 
 def parse_datestr(datestr):
     """Parses a datetime object from the string format shared by xml and notation serializations."""
@@ -183,6 +186,7 @@ class LLSDXMLFormatter(object):
             unicode : self.STRING,
             uri : self.URI,
             datetime.datetime : self.DATE,
+            datetime.date : self.DATE,
             list : self.ARRAY,
             tuple : self.ARRAY,
             types.GeneratorType : self.ARRAY,
@@ -347,6 +351,7 @@ class LLSDNotationFormatter(object):
             unicode : self.STRING,
             uri : self.URI,
             datetime.datetime : self.DATE,
+            datetime.date : self.DATE,
             list : self.ARRAY,
             tuple : self.ARRAY,
             types.GeneratorType : self.ARRAY,
@@ -924,12 +929,13 @@ def _format_binary_recurse(something):
                 (type(something), something))
 
 
-def parse_binary(something):
-    header = '<?llsd/binary?>\n'
-    if not something.startswith(header):
-        raise LLSDParseError('LLSD binary encoding header not found')
-    return LLSDBinaryParser().parse(something[len(header):])
-    
+def parse_binary(binary):
+    if binary.startswith('<?llsd/binary?>'):
+        just_binary = binary.split('\n', 1)[1]
+    else:
+        just_binary = binary
+    return LLSDBinaryParser().parse(just_binary)
+
 def parse_xml(something):
     try:
         return to_python(fromstring(something)[0])
diff --git a/indra/lib/python/indra/base/metrics.py b/indra/lib/python/indra/base/metrics.py
index 8f2a85cf0e..ff8380265f 100644
--- a/indra/lib/python/indra/base/metrics.py
+++ b/indra/lib/python/indra/base/metrics.py
@@ -29,25 +29,93 @@ $/LicenseInfo$
 """
 
 import sys
-from indra.base import llsd
+try:
+    import syslog
+except ImportError:
+    # Windows
+    import sys
+    class syslog(object):
+        # wrap to a lame syslog for windows
+        _logfp = sys.stderr
+        def syslog(msg):
+            _logfp.write(msg)
+            if not msg.endswith('\n'):
+                _logfp.write('\n')
+        syslog = staticmethod(syslog)
 
-_sequence_id = 0
+from indra.base.llsd import format_notation
 
-def record_metrics(table, stats, dest=None):
+def record_metrics(table, stats):
     "Write a standard metrics log"
-    _log("LLMETRICS", table, stats, dest)
+    _log("LLMETRICS", table, stats)
 
-def record_event(table, data, dest=None):
+def record_event(table, data):
     "Write a standard logmessage log"
-    _log("LLLOGMESSAGE", table, data, dest)
+    _log("LLLOGMESSAGE", table, data)
+
+def set_destination(dest):
+    """Set the destination of metrics logs for this process.
 
-def _log(header, table, data, dest):
+    If you do not call this function prior to calling a logging
+    method, that function will open sys.stdout as a destination.
+    Attempts to set dest to None will throw a RuntimeError.
+    @param dest a file-like object which will be the destination for logs."""
     if dest is None:
-        # do this check here in case sys.stdout changes at some
-        # point. as a default parameter, it will never be
-        # re-evaluated.
-        dest = sys.stdout
+        raise RuntimeError("Attempt to unset metrics destination.")
+    global _destination
+    _destination = dest
+
+def destination():
+    """Get the destination of the metrics logs for this process.
+    Returns None if no destination is set"""
+    global _destination
+    return _destination
+
+class SysLogger(object):
+    "A file-like object which writes to syslog."
+    def __init__(self, ident='indra', logopt = None, facility = None):
+        try:
+            if logopt is None:
+                logopt = syslog.LOG_CONS | syslog.LOG_PID
+            if facility is None:
+                facility = syslog.LOG_LOCAL0
+            syslog.openlog(ident, logopt, facility)
+            import atexit
+            atexit.register(syslog.closelog)
+        except AttributeError:
+            # No syslog module on Windows
+            pass
+
+    def write(str):
+        syslog.syslog(str)
+    write = staticmethod(write)
+
+    def flush():
+        pass
+    flush = staticmethod(flush)
+
+#
+# internal API
+#
+_sequence_id = 0
+_destination = None
+
+def _next_id():
     global _sequence_id
-    print >>dest, header, "(" + str(_sequence_id) + ")",
-    print >>dest, table, llsd.format_notation(data)
+    next = _sequence_id
     _sequence_id += 1
+    return next
+
+def _dest():
+    global _destination
+    if _destination is None:
+        # this default behavior is documented in the metrics functions above.
+        _destination = sys.stdout
+    return _destination
+    
+def _log(header, table, data):
+    log_line = "%s (%d) %s %s" \
+               % (header, _next_id(), table, format_notation(data))
+    dest = _dest()
+    dest.write(log_line)
+    dest.flush()
diff --git a/indra/lib/python/indra/ipc/servicebuilder.py b/indra/lib/python/indra/ipc/servicebuilder.py
index cb43bcb26f..0a0ce2b4e2 100644
--- a/indra/lib/python/indra/ipc/servicebuilder.py
+++ b/indra/lib/python/indra/ipc/servicebuilder.py
@@ -39,6 +39,12 @@ except:
     pass
 
 _g_builder = None
+def _builder():
+    global _g_builder
+    if _g_builder is None:
+        _g_builder = ServiceBuilder()
+    return _g_builder
+
 def build(name, context={}, **kwargs):
     """ Convenience method for using a global, singleton, service builder.  Pass arguments either via a dict or via python keyword arguments, or both!
 
@@ -56,6 +62,11 @@ def build(name, context={}, **kwargs):
         _g_builder = ServiceBuilder()
     return _g_builder.buildServiceURL(name, context, **kwargs)
 
+def build_path(name, context={}, **kwargs):
+    context = context.copy()  # shouldn't modify the caller's dictionary
+    context.update(kwargs)
+    return _builder().buildPath(name, context)
+
 class ServiceBuilder(object):
     def __init__(self, services_definition = services_config):
         """\
@@ -73,12 +84,21 @@ class ServiceBuilder(object):
                 continue
             if isinstance(service_builder, dict):
                 # We will be constructing several builders
-                for name, builder in service_builder.items():
+                for name, builder in service_builder.iteritems():
                     full_builder_name = service['name'] + '-' + name
                     self.builders[full_builder_name] = builder
             else:
                 self.builders[service['name']] = service_builder
 
+    def buildPath(self, name, context):
+        """\
+        @brief given the environment on construction, return a service path.
+        @param name The name of the service.
+        @param context A dict of name value lookups for the service.
+        @returns Returns the 
+        """
+        return russ.format(self.builders[name], context)
+
     def buildServiceURL(self, name, context={}, **kwargs):
         """\
         @brief given the environment on construction, return a service URL.
diff --git a/indra/lib/python/indra/ipc/siesta.py b/indra/lib/python/indra/ipc/siesta.py
index b206f181c4..d867e71537 100644
--- a/indra/lib/python/indra/ipc/siesta.py
+++ b/indra/lib/python/indra/ipc/siesta.py
@@ -1,3 +1,32 @@
+"""\
+@file siesta.py
+@brief A tiny llsd based RESTful web services framework
+
+$LicenseInfo:firstyear=2008&license=mit$
+
+Copyright (c) 2008, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
+
+from indra.base import config
 from indra.base import llsd
 from webob import exc
 import webob
@@ -37,11 +66,11 @@ def mime_type(content_type):
     return content_type.split(';', 1)[0].strip().lower()
     
 class BodyLLSD(object):
-    '''Give a webob Request or Response an llsd property.
+    '''Give a webob Request or Response an llsd based "content" property.
 
-    Getting the llsd property parses the body, and caches the result.
+    Getting the content property parses the body, and caches the result.
 
-    Setting the llsd property formats a payload, and the body property
+    Setting the content property formats a payload, and the body property
     is set.'''
 
     def _llsd__get(self):
@@ -80,7 +109,7 @@ class BodyLLSD(object):
         if hasattr(self, '_llsd'):
             del self._llsd
 
-    llsd = property(_llsd__get, _llsd__set, _llsd__del)
+    content = property(_llsd__get, _llsd__set, _llsd__del)
 
 
 class Response(webob.Response, BodyLLSD):
@@ -114,10 +143,10 @@ class Request(webob.Request, BodyLLSD):
 
     Sensible content type and accept headers are used by default.
 
-    Setting the llsd property also sets the body.  Getting the llsd
+    Setting the content property also sets the body. Getting the content
     property parses the body if necessary.
 
-    If you set the body property directly, the llsd property will be
+    If you set the body property directly, the content property will be
     deleted.'''
     
     default_content_type = 'application/llsd+xml'
@@ -149,11 +178,11 @@ class Request(webob.Request, BodyLLSD):
     body = property(webob.Request._body__get, _body__set,
                     webob.Request._body__del, webob.Request._body__get.__doc__)
 
-    def create_response(self, llsd=None, status='200 OK',
+    def create_response(self, content=None, status='200 OK',
                         conditional_response=webob.NoDefault):
         resp = self.ResponseClass(status=status, request=self,
                                   conditional_response=conditional_response)
-        resp.llsd = llsd
+        resp.content = content
         return resp
 
     def curl(self):
@@ -196,12 +225,18 @@ llsd_formatters = {
     'application/xml': llsd.format_xml,
     }
 
+formatter_qualities = (
+    ('application/llsd+xml', 1.0),
+    ('application/llsd+notation', 0.5),
+    ('application/llsd+binary', 0.4),
+    ('application/xml', 0.3),
+    ('application/json', 0.2),
+    )
 
 def formatter_for_mime_type(mime_type):
     '''Return a formatter that encodes to the given MIME type.
 
     The result is a pair of function and MIME type.'''
-
     try:
         return llsd_formatters[mime_type], mime_type
     except KeyError:
@@ -214,21 +249,19 @@ def formatter_for_request(req):
     '''Return a formatter that encodes to the preferred type of the client.
 
     The result is a pair of function and actual MIME type.'''
-
-    for ctype in req.accept.best_matches('application/llsd+xml'):
-        try:
-            return llsd_formatters[ctype], ctype
-        except KeyError:
-            pass
-    else:
+    ctype = req.accept.best_match(formatter_qualities)
+    try:
+        return llsd_formatters[ctype], ctype
+    except KeyError:
         raise exc.HTTPNotAcceptable().exception
 
 
 def wsgi_adapter(func, environ, start_response):
     '''Adapt a Siesta callable to act as a WSGI application.'''
-
+    # Process the request as appropriate.
     try:
         req = Request(environ)
+        #print req.urlvars
         resp = func(req, **req.urlvars)
         if not isinstance(resp, webob.Response):
             try:
@@ -281,7 +314,8 @@ def llsd_class(cls):
             allowed = [m for m in http11_methods
                        if hasattr(instance, 'handle_' + m.lower())]
             raise exc.HTTPMethodNotAllowed(
-                headers={'Allowed': ', '.join(allowed)}).exception
+                headers={'Allow': ', '.join(allowed)}).exception
+        #print "kwargs: ", kwargs
         return handler(req, **kwargs)
 
     def replacement(environ, start_response):
@@ -336,7 +370,7 @@ def curl(reqs):
 
 route_re = re.compile(r'''
     \{                 # exact character "{"
-    (\w+)              # variable name (restricted to a-z, 0-9, _)
+    (\w*)              # "config" or variable (restricted to a-z, 0-9, _)
     (?:([:~])([^}]+))? # optional :type or ~regex part
     \}                 # exact character "}"
     ''', re.VERBOSE)
@@ -344,27 +378,37 @@ route_re = re.compile(r'''
 predefined_regexps = {
     'uuid': r'[a-f0-9][a-f0-9-]{31,35}',
     'int': r'\d+',
+    'host': r'[a-z0-9][a-z0-9\-\.]*',
     }
 
 def compile_route(route):
     fp = StringIO()
     last_pos = 0
     for match in route_re.finditer(route):
+        #print "matches: ", match.groups()
         fp.write(re.escape(route[last_pos:match.start()]))
         var_name = match.group(1)
         sep = match.group(2)
         expr = match.group(3)
-        if expr:
-            if sep == ':':
-                expr = predefined_regexps[expr]
-            # otherwise, treat what follows '~' as a regexp
+        if var_name == 'config':
+            expr = re.escape(str(config.get(var_name)))
         else:
-            expr = '[^/]+'
-        expr = '(?P<%s>%s)' % (var_name, expr)
+            if expr:
+                if sep == ':':
+                    expr = predefined_regexps[expr]
+                # otherwise, treat what follows '~' as a regexp
+            else:
+                expr = '[^/]+'
+            if var_name != '':
+                expr = '(?P<%s>%s)' % (var_name, expr)
+            else:
+                expr = '(%s)' % (expr,)
         fp.write(expr)
         last_pos = match.end()
     fp.write(re.escape(route[last_pos:]))
-    return '^%s$' % fp.getvalue()
+    compiled_route = '^%s$' % fp.getvalue()
+    #print route, "->", compiled_route
+    return compiled_route
 
 class Router(object):
     '''WSGI routing class.  Parses a URL and hands off a request to
@@ -372,21 +416,43 @@ class Router(object):
     responds with a 404.'''
 
     def __init__(self):
-        self.routes = []
-        self.paths = []
+        self._new_routes = []
+        self._routes = []
+        self._paths = []
 
     def add(self, route, app, methods=None):
-        self.paths.append(route)
-        self.routes.append((re.compile(compile_route(route)), app,
-                            methods and dict.fromkeys(methods)))
+        self._new_routes.append((route, app, methods))
+
+    def _create_routes(self):
+        for route, app, methods in self._new_routes:
+            self._paths.append(route)
+            self._routes.append(
+                (re.compile(compile_route(route)),
+                 app,
+                 methods and dict.fromkeys(methods)))
+        self._new_routes = []
 
     def __call__(self, environ, start_response):
+        # load up the config from the config file. Only needs to be
+        # done once per interpreter. This is the entry point of all
+        # siesta applications, so this is where we trap it.
+        _conf = config.get_config()
+        if _conf is None:
+            import os.path
+            fname = os.path.join(
+                environ.get('ll.config_dir', '/local/linden/etc'),
+                'indra.xml')
+            config.load(fname)
+
+        # proceed with handling the request
+        self._create_routes()
         path_info = environ['PATH_INFO']
         request_method = environ['REQUEST_METHOD']
         allowed = []
-        for regex, app, methods in self.routes:
+        for regex, app, methods in self._routes:
             m = regex.match(path_info)
             if m:
+                #print "groupdict:",m.groupdict()
                 if not methods or request_method in methods:
                     environ['paste.urlvars'] = m.groupdict()
                     return app(environ, start_response)
@@ -396,7 +462,7 @@ class Router(object):
             allowed = dict.fromkeys(allows).keys()
             allowed.sort()
             resp = exc.HTTPMethodNotAllowed(
-                headers={'Allowed': ', '.join(allowed)})
+                headers={'Allow': ', '.join(allowed)})
         else:
             resp = exc.HTTPNotFound()
         return resp(environ, start_response)
diff --git a/indra/lib/python/indra/util/named_query.py b/indra/lib/python/indra/util/named_query.py
index 59c37a7218..cdce8237c8 100644
--- a/indra/lib/python/indra/util/named_query.py
+++ b/indra/lib/python/indra/util/named_query.py
@@ -47,10 +47,8 @@ except NameError:
 from indra.base import llsd
 from indra.base import config
 
-DEBUG = False
-
-NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '.nq')
-NQ_FILE_SUFFIX_LEN  = len(NQ_FILE_SUFFIX)
+NQ_FILE_SUFFIX = None
+NQ_FILE_SUFFIX_LEN = None
 
 _g_named_manager = None
 
@@ -60,6 +58,11 @@ def _init_g_named_manager(sql_dir = None):
 
     This function is intended entirely for testing purposes,
     because it's tricky to control the config from inside a test."""
+    global NQ_FILE_SUFFIX
+    NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '.nq')
+    global NQ_FILE_SUFFIX_LEN
+    NQ_FILE_SUFFIX_LEN  = len(NQ_FILE_SUFFIX)
+
     if sql_dir is None:
         sql_dir = config.get('named-query-base-dir')
 
@@ -73,11 +76,11 @@ def _init_g_named_manager(sql_dir = None):
     _g_named_manager = NamedQueryManager(
         os.path.abspath(os.path.realpath(sql_dir)))
 
-def get(name):
+def get(name, schema = None):
     "Get the named query object to be used to perform queries"
     if _g_named_manager is None:
         _init_g_named_manager()
-    return _g_named_manager.get(name)
+    return _g_named_manager.get(name).for_schema(schema)
 
 def sql(connection, name, params):
     # use module-global NamedQuery object to perform default substitution
@@ -330,6 +333,8 @@ class NamedQuery(object):
 
     def for_schema(self, db_name):
         "Look trough the alternates and return the correct query"
+        if db_name is None:
+            return self
         try:
             return self._alternative[db_name]
         except KeyError, e:
@@ -359,10 +364,10 @@ class NamedQuery(object):
         if DEBUG:
             print "SQL:", self.sql(connection, params)
         rows = cursor.execute(full_query, params)
-        
+
         # *NOTE: the expect_rows argument is a very cheesy way to get some
         # validation on the result set.  If you want to add more expectation
-        # logic, do something more object-oriented and flexible.  Or use an ORM.
+        # logic, do something more object-oriented and flexible. Or use an ORM.
         if(self._return_as_map):
             expect_rows = 1
         if expect_rows is not None and rows != expect_rows:
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 3f14be6e18..d6a9e10707 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -32,7 +32,6 @@ set(llcommon_SOURCE_FILES
     llformat.cpp
     llframetimer.cpp
     llheartbeat.cpp
-    llindraconfigfile.cpp
     llliveappconfig.cpp
     lllivefile.cpp
     lllog.cpp
@@ -118,7 +117,6 @@ set(llcommon_HEADER_FILES
     llheartbeat.h
     llhttpstatuscodes.h
     llindexedqueue.h
-    llindraconfigfile.h
     llkeythrottle.h
     lllinkedqueue.h
     llliveappconfig.h
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index 199315f34e..968b92d1e7 100644
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -38,7 +38,9 @@
 #include "llerrorcontrol.h"
 #include "llerrorthread.h"
 #include "llframetimer.h"
+#include "lllivefile.h"
 #include "llmemory.h"
+#include "llstl.h" // for DeletePointer()
 #include "lltimer.h"
 
 //
@@ -91,7 +93,6 @@ LLAppChildCallback LLApp::sDefaultChildCallback = NULL;
 LLApp::LLApp() : mThreadErrorp(NULL)
 {
 	commonCtor();
-	startErrorThread();
 }
 
 void LLApp::commonCtor()
@@ -106,9 +107,6 @@ void LLApp::commonCtor()
 	sSigChildCount = new LLAtomicU32(0);
 #endif
 
-	// Setup error handling
-	setupErrorHandling();
-
 	// initialize the options structure. We need to make this an array
 	// because the structured data will not auto-allocate if we
 	// reference an invalid location with the [] operator.
@@ -141,6 +139,11 @@ LLApp::~LLApp()
 	delete sSigChildCount;
 	sSigChildCount = NULL;
 #endif
+
+	// reclaim live file memory
+	std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer());
+	mLiveFiles.clear();
+
 	setStopped();
 	// HACK: wait for the error thread to clean itself
 	ms_sleep(20);
@@ -214,6 +217,15 @@ bool LLApp::parseCommandOptions(int argc, char** argv)
 	return true;
 }
 
+
+void LLApp::manageLiveFile(LLLiveFile* livefile)
+{
+	if(!livefile) return;
+	livefile->checkAndReload();
+	livefile->addToEventTimer();
+	mLiveFiles.push_back(livefile);
+}
+
 bool LLApp::setOptionData(OptionPriority level, LLSD data)
 {
 	if((level < 0)
@@ -275,6 +287,7 @@ void LLApp::setupErrorHandling()
 
 #endif
 
+	startErrorThread();
 }
 
 void LLApp::startErrorThread()
@@ -283,10 +296,13 @@ void LLApp::startErrorThread()
 	// Start the error handling thread, which is responsible for taking action
 	// when the app goes into the APP_STATUS_ERROR state
 	//
-	llinfos << "Starting error thread" << llendl;
-	mThreadErrorp = new LLErrorThread();
-	mThreadErrorp->setUserData((void *) this);
-	mThreadErrorp->start();	
+	if(!mThreadErrorp)
+	{
+		llinfos << "Starting error thread" << llendl;
+		mThreadErrorp = new LLErrorThread();
+		mThreadErrorp->setUserData((void *) this);
+		mThreadErrorp->start();
+	}
 }
 
 void LLApp::setErrorHandler(LLAppErrorHandler handler)
diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h
index f8a593c33d..cc60ba0b80 100644
--- a/indra/llcommon/llapp.h
+++ b/indra/llcommon/llapp.h
@@ -40,8 +40,7 @@
 
 // Forward declarations
 class LLErrorThread;
-class LLApp;
-
+class LLLiveFile;
 
 typedef void (*LLAppErrorHandler)();
 typedef void (*LLAppChildCallback)(int pid, bool exited, int status);
@@ -127,6 +126,19 @@ public:
 	 */
 	bool parseCommandOptions(int argc, char** argv);
 
+	/**
+	 * @brief Keep track of live files automatically.
+	 *
+	 * *TODO: it currently uses the <code>addToEventTimer()</code> API
+	 * instead of the runner. I should probalby use the runner.
+	 *
+	 * *NOTE: DO NOT add the livefile instance to any kind of check loop.
+	 *
+	 * @param livefile A valid instance of an LLLiveFile. This LLApp
+	 * instance will delete the livefile instance.
+	 */
+	void manageLiveFile(LLLiveFile* livefile);
+
 	/**
 	 * @brief Set the options at the specified priority.
 	 *
@@ -194,11 +206,26 @@ public:
 #endif
 	static int getPid();
 
-	//
-	// Error handling methods
-	//
+	/** @name Error handling methods */
+	//@{
+	/**
+	 * @brief Do our generic platform-specific error-handling setup --
+	 * signals on unix, structured exceptions on windows.
+	 * 
+	 * DO call this method if your app will either spawn children or be
+	 * spawned by a launcher.
+	 * Call just after app object construction.
+	 * (Otherwise your app will crash when getting signals,
+	 * and will not core dump.)
+	 *
+	 * DO NOT call this method if your application has specialized
+	 * error handling code.
+	 */
+	void setupErrorHandling();
+
 	void setErrorHandler(LLAppErrorHandler handler);
 	void setSyncErrorHandler(LLAppErrorHandler handler);
+	//@}
 
 #if !LL_WINDOWS
 	//
@@ -214,8 +241,9 @@ public:
 	void setDefaultChildCallback(LLAppChildCallback callback); 
 	
     // Fork and do the proper signal handling/error handling mojo
-	// WARNING: You need to make sure your signal handling callback is correct after
-	// you fork, because not all threads are duplicated when you fork!
+	// *NOTE: You need to make sure your signal handling callback is
+	// correct after you fork, because not all threads are duplicated
+	// when you fork!
 	pid_t fork(); 
 #endif
 
@@ -255,7 +283,6 @@ protected:
 private:
 	void startErrorThread();
 	
-	void setupErrorHandling();		// Do platform-specific error-handling setup (signals, structured exceptions)
 	static void runErrorHandler(); // run shortly after we detect an error, ran in the relatively robust context of the LLErrorThread - preferred.
 	static void runSyncErrorHandler(); // run IMMEDIATELY when we get an error, ran in the context of the faulting thread.
 
@@ -278,6 +305,8 @@ private:
 	// The application options.
 	LLSD mOptions;
 
+	// The live files for this application
+	std::vector<LLLiveFile*> mLiveFiles;
 	//@}
 
 private:
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index e8c95d0a76..d671decccb 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -289,7 +289,7 @@ namespace
 	public:
 		static LogControlFile& fromDirectory(const std::string& dir);
 		
-		virtual void loadFile();
+		virtual bool loadFile();
 		
 	private:
 		LogControlFile(const std::string &filename)
@@ -317,7 +317,7 @@ namespace
 			// NB: This instance is never freed
 	}
 	
-	void LogControlFile::loadFile()
+	bool LogControlFile::loadFile()
 	{
 		LLSD configuration;
 
@@ -333,12 +333,13 @@ namespace
 				llwarns << filename() << " missing, ill-formed,"
 							" or simply undefined; not changing configuration"
 						<< llendl;
-				return;
+				return false;
 			}
 		}
 		
 		LLError::configure(configuration);
 		llinfos << "logging reconfigured from " << filename() << llendl;
+		return true;
 	}
 
 
diff --git a/indra/llcommon/llliveappconfig.cpp b/indra/llcommon/llliveappconfig.cpp
index e1bfc11a03..75bdfee8b7 100644
--- a/indra/llcommon/llliveappconfig.cpp
+++ b/indra/llcommon/llliveappconfig.cpp
@@ -38,9 +38,12 @@
 #include "llsd.h"
 #include "llsdserialize.h"
 
-LLLiveAppConfig::LLLiveAppConfig(LLApp* app, const std::string& filename, F32 refresh_period)
-:	LLLiveFile(filename, refresh_period),
-	mApp(app)
+LLLiveAppConfig::LLLiveAppConfig(
+	const std::string& filename,
+	F32 refresh_period,
+	LLApp::OptionPriority priority) :
+	LLLiveFile(filename, refresh_period),
+	mPriority(priority)
 { }
 
 
@@ -48,7 +51,7 @@ LLLiveAppConfig::~LLLiveAppConfig()
 { }
 
 // virtual 
-void LLLiveAppConfig::loadFile()
+bool LLLiveAppConfig::loadFile()
 {
 	llinfos << "LLLiveAppConfig::loadFile(): reading from "
 		<< filename() << llendl;
@@ -59,12 +62,25 @@ void LLLiveAppConfig::loadFile()
         LLSDSerialize::fromXML(config, file);
 		if(!config.isMap())
 		{
-			llinfos << "LLDataserverConfig::loadFile(): not an map!"
+			llwarns << "Live app config not an map in " << filename()
 				<< " Ignoring the data." << llendl;
-			return;
+			return false;
 		}
 		file.close();
     }
-	mApp->setOptionData(
-		LLApp::PRIORITY_SPECIFIC_CONFIGURATION, config);
+	else
+	{
+		llinfos << "Live file " << filename() << " does not exit." << llendl;
+	}
+	// *NOTE: we do not handle the else case here because we would not
+	// have attempted to load the file unless LLLiveFile had
+	// determined there was a reason to load it. This only happens
+	// when either the file has been updated or it is either suddenly
+	// in existence or has passed out of existence. Therefore, we want
+	// to set the config to an empty config, and return that it
+	// changed.
+
+	LLApp* app = LLApp::instance();
+	if(app) app->setOptionData(mPriority, config);
+	return true;
 }
diff --git a/indra/llcommon/llliveappconfig.h b/indra/llcommon/llliveappconfig.h
index 55d84a4778..a6ece6e8b3 100644
--- a/indra/llcommon/llliveappconfig.h
+++ b/indra/llcommon/llliveappconfig.h
@@ -33,25 +33,43 @@
 #ifndef LLLIVEAPPCONFIG_H
 #define LLLIVEAPPCONFIG_H
 
+#include "llapp.h"
 #include "lllivefile.h"
 
-class LLApp;
 
+/**
+ * @class LLLiveAppConfig
+ * @see LLLiveFile
+ *
+ * To use this, instantiate a LLLiveAppConfig object inside your main
+ * loop.  The traditional name for it is live_config.  Be sure to call
+ * <code>live_config.checkAndReload()</code> periodically.
+ */
 class LLLiveAppConfig : public LLLiveFile
 {
 public:
-	// To use this, instantiate a LLLiveAppConfig object inside your main loop.
-	// The traditional name for it is live_config.
-	// Be sure to call live_config.checkAndReload() periodically.
 
-	LLLiveAppConfig(LLApp* app, const std::string& filename, F32 refresh_period);
-	~LLLiveAppConfig();
+	/**
+	 * @brief Constructor
+	 *
+	 * @param filename. The name of the file for periodically checking
+	 * configuration.
+	 * @param refresh_period How often the internal timer should
+	 * bother checking the filesystem.
+	 * @param The application priority level of that configuration file.
+	 */
+	LLLiveAppConfig(
+		const std::string& filename,
+		F32 refresh_period,
+		LLApp::OptionPriority priority);
+
+	~LLLiveAppConfig(); ///< Destructor
 
 protected:
-	/*virtual*/ void loadFile();
+	/*virtual*/ bool loadFile();
 
 private:
-	LLApp* mApp;
+	LLApp::OptionPriority mPriority;
 };
 
 #endif
diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp
index b6f458cb3e..effda6c49c 100644
--- a/indra/llcommon/lllivefile.cpp
+++ b/indra/llcommon/lllivefile.cpp
@@ -35,14 +35,17 @@
 #include "llframetimer.h"
 #include "lltimer.h"
 
+const F32 DEFAULT_CONFIG_FILE_REFRESH = 5.0f;
+
+
 class LLLiveFile::Impl
 {
 public:
-	Impl(const std::string &filename, const F32 refresh_period);
+	Impl(const std::string& filename, const F32 refresh_period);
 	~Impl();
 	
 	bool check();
-	
+	void changed();
 	
 	bool mForceCheck;
 	F32 mRefreshPeriod;
@@ -50,16 +53,19 @@ public:
 
 	std::string mFilename;
 	time_t mLastModTime;
+	time_t mLastStatTime;
 	bool mLastExists;
 	
 	LLEventTimer* mEventTimer;
 };
 
-LLLiveFile::Impl::Impl(const std::string &filename, const F32 refresh_period)
-	: mForceCheck(true),
+LLLiveFile::Impl::Impl(const std::string& filename, const F32 refresh_period)
+	:
+	mForceCheck(true),
 	mRefreshPeriod(refresh_period),
 	mFilename(filename),
 	mLastModTime(0),
+	mLastStatTime(0),
 	mLastExists(false),
 	mEventTimer(NULL)
 {
@@ -70,7 +76,7 @@ LLLiveFile::Impl::~Impl()
 	delete mEventTimer;
 }
 
-LLLiveFile::LLLiveFile(const std::string &filename, const F32 refresh_period)
+LLLiveFile::LLLiveFile(const std::string& filename, const F32 refresh_period)
 	: impl(* new Impl(filename, refresh_period))
 {
 }
@@ -121,17 +127,30 @@ bool LLLiveFile::Impl::check()
 
 	// We want to read the file.  Update status info for the file.
 	mLastExists = true;
-	mLastModTime = stat_data.st_mtime;
-	
+	mLastStatTime = stat_data.st_mtime;
 	return true;
 }
 
+void LLLiveFile::Impl::changed()
+{
+	// we wanted to read this file, and we were successful.
+	mLastModTime = mLastStatTime;
+}
+
 bool LLLiveFile::checkAndReload()
 {
 	bool changed = impl.check();
 	if (changed)
 	{
-		loadFile();
+		if(loadFile())
+		{
+			impl.changed();
+			this->changed();
+		}
+		else
+		{
+			changed = false;
+		}
 	}
 	return changed;
 }
diff --git a/indra/llcommon/lllivefile.h b/indra/llcommon/lllivefile.h
index a3a9cf49ab..89b5d95e44 100644
--- a/indra/llcommon/lllivefile.h
+++ b/indra/llcommon/lllivefile.h
@@ -33,29 +33,65 @@
 #ifndef LL_LLLIVEFILE_H
 #define LL_LLLIVEFILE_H
 
-const F32 configFileRefreshRate = 5.0; // seconds
+extern const F32 DEFAULT_CONFIG_FILE_REFRESH;
 
 
 class LLLiveFile
 {
 public:
-	LLLiveFile(const std::string &filename, const F32 refresh_period = 5.f);
+	LLLiveFile(const std::string& filename, const F32 refresh_period = 5.f);
 	virtual ~LLLiveFile();
 
+	/**
+	 * @brief Check to see if this live file should reload.
+	 *
+	 * Call this before using anything that was read & cached
+	 * from the file.
+	 *
+	 * This method calls the <code>loadFile()</code> method if
+	 * any of:
+	 *   file has a new modify time since the last check
+	 *   file used to exist and now does not
+	 *   file used to not exist but now does
+	 * @return Returns true if the file was reloaded.
+	 */
 	bool checkAndReload();
-		// Returns true if the file changed in any way
-		// Call this before using anything that was read & cached from the file
+	
 
 	std::string filename() const;
 
+	/**
+	 * @brief Add this live file to an automated recheck.
+	 *
+	 * Normally, just calling checkAndReload() is enough. In some
+	 * cases though, you may need to let the live file periodically
+	 * check itself.
+	 */
 	void addToEventTimer();
-		// Normally, just calling checkAndReload() is enough.  In some cases
-		// though, you may need to let the live file periodically check itself.
 
 	void setRefreshPeriod(F32 seconds);
 
 protected:
-	virtual void loadFile() = 0; // Implement this to load your file if it changed
+	/**
+	 * @breif Implement this to load your file if it changed.
+	 *
+	 * This method is called automatically by <code>checkAndReload()</code>,
+	 * so though you must implement this in derived classes, you do
+	 * not need to call it manually.
+	 * @return Returns true if the file was successfully loaded.
+	 */
+	virtual bool loadFile() = 0;
+
+	/**
+	 * @brief Implement this method if you want to get a change callback.
+	 *
+	 * This virtual function will be called automatically at the end
+	 * of <code>checkAndReload()</code> if a new configuration was
+	 * loaded. This does not track differences between the current and
+	 * newly loaded file, so any successful load event will trigger a
+	 * <code>changed()</code> callback. Default is to do nothing.
+	 */
+	virtual void changed() {}
 
 private:
 	class Impl;
diff --git a/indra/llcommon/llstat.cpp b/indra/llcommon/llstat.cpp
index e411a1c798..291b019616 100644
--- a/indra/llcommon/llstat.cpp
+++ b/indra/llcommon/llstat.cpp
@@ -62,7 +62,7 @@ public:
     static std::string filename();
     
 protected:
-    /* virtual */ void loadFile();
+    /* virtual */ bool loadFile();
 
 public:
     void init(LLPerfStats* statsp);
@@ -94,12 +94,12 @@ LLStatsConfigFile& LLStatsConfigFile::instance()
 
 /* virtual */
 // Load and parse the stats configuration file
-void LLStatsConfigFile::loadFile()
+bool LLStatsConfigFile::loadFile()
 {
     if (!mStatsp)
     {
         llwarns << "Tries to load performance configure file without initializing LPerfStats" << llendl;
-        return;
+        return false;
     }
     mChanged = true;
     
@@ -113,7 +113,7 @@ void LLStatsConfigFile::loadFile()
             {
                 llinfos << "Performance statistics configuration file ill-formed, not recording statistics" << llendl;
                 mStatsp->setReportPerformanceDuration( 0.f );
-                return;
+                return false;
             }
         }
         else 
@@ -123,7 +123,7 @@ void LLStatsConfigFile::loadFile()
                 llinfos << "Performance statistics configuration file deleted, not recording statistics" << llendl;
                 mStatsp->setReportPerformanceDuration( 0.f );
             }
-            return;
+            return true;
         }
     }
 
@@ -159,6 +159,7 @@ void LLStatsConfigFile::loadFile()
     {
         llinfos << "Performance stats recording turned off" << llendl;
     }
+	return true;
 }
 
 
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index 99a9b9e269..6ba665b8d2 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -228,7 +228,25 @@ public:
 	
 	// True if this is the head of s.
 	static BOOL	isHead( const std::basic_string<T>& string, const T* s ); 
-	
+
+	/**
+	 * @brief Returns true if string starts with substr
+	 *
+	 * If etither string or substr are empty, this method returns false.
+	 */
+	static bool startsWith(
+		const std::basic_string<T>& string,
+		const std::basic_string<T>& substr);
+
+	/**
+	 * @brief Returns true if string ends in substr
+	 *
+	 * If etither string or substr are empty, this method returns false.
+	 */
+	static bool endsWith(
+		const std::basic_string<T>& string,
+		const std::basic_string<T>& substr);
+
 	static void	addCRLF(std::basic_string<T>& string);
 	static void	removeCRLF(std::basic_string<T>& string);
 
@@ -335,7 +353,7 @@ public:
  * This function works on bytes rather than glyphs, so this will
  * incorrectly truncate non-single byte strings.
  * Use utf8str_truncate() for utf8 strings
- * @return a copy of in string minus the trailing count characters.
+ * @return a copy of in string minus the trailing count bytes.
  */
 inline std::string chop_tail_copy(
 	const std::string& in,
@@ -1065,6 +1083,30 @@ BOOL LLStringUtilBase<T>::isHead( const std::basic_string<T>& string, const T* s
 	}
 }
 
+// static
+template<class T> 
+bool LLStringUtilBase<T>::startsWith(
+	const std::basic_string<T>& string,
+	const std::basic_string<T>& substr)
+{
+	if(string.empty() || (substr.empty())) return false;
+	if(0 == string.find(substr)) return true;
+	return false;
+}
+
+// static
+template<class T> 
+bool LLStringUtilBase<T>::endsWith(
+	const std::basic_string<T>& string,
+	const std::basic_string<T>& substr)
+{
+	if(string.empty() || (substr.empty())) return false;
+	std::string::size_type idx = string.rfind(substr);
+	if(std::string::npos == idx) return false;
+	return (idx == (string.size() - substr.size()));
+}
+
+
 template<class T> 
 BOOL LLStringUtilBase<T>::convertToBOOL(const std::basic_string<T>& string, BOOL& value)
 {
diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp
index 3dbc837875..f6e8f01f0e 100644
--- a/indra/llcommon/lluri.cpp
+++ b/indra/llcommon/lluri.cpp
@@ -162,11 +162,10 @@ namespace
 		{ return LLURI::escape(s, unreserved() + ":@!$'()*+,="); }	// sub_delims - "&;" + ":@"
 }
 
-// *TODO: Consider using curl. After http textures gets merged everywhere.
-// static
+//static
 std::string LLURI::escape(const std::string& str)
 {
-	static std::string default_allowed(unreserved() + ":@!$'()*+,=/?&#;");
+	static std::string default_allowed = unreserved();
 	static bool initialized = false;
 	if(!initialized)
 	{
diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h
index 156d80b97e..8e46e2e89e 100644
--- a/indra/llcommon/lluri.h
+++ b/indra/llcommon/lluri.h
@@ -127,27 +127,16 @@ public:
 	/** @name Escaping Utilities */
 	//@{
 	/**
-	 * @brief Escape a raw url with a reasonable set of allowed characters.
-	 *
-	 * The default set was chosen to match HTTP urls and general
-     *  guidelines for naming resources. Passing in a raw url does not
-     *  produce well defined results because you really need to know
-     *  which segments are path parts because path parts are supposed
-     *  to be escaped individually. The default set chosen is:
+	 * @brief Escape the string passed except for unreserved
 	 *
 	 *  ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
 	 *  0123456789
 	 *  -._~
-	 *  :@!$'()*+,=/?&#;
 	 *
-	 * *NOTE: This API is basically broken because it does not
-     *  allow you to specify significant path characters. For example,
-     *  if the filename actually contained a /, then you cannot use
-     *  this function to generate the serialized url for that
-     *  resource.
+	 * @see http://www.ietf.org/rfc/rfc1738.txt
 	 *
 	 * @param str The raw URI to escape.
-	 * @return Returns the escaped uri or an empty string.
+	 * @return Returns the rfc 1738 escaped uri or an empty string.
 	 */
 	static std::string escape(const std::string& str);
 
diff --git a/indra/llcrashlogger/llcrashlogger.cpp b/indra/llcrashlogger/llcrashlogger.cpp
index 78c4f8e742..2fd37e848e 100755
--- a/indra/llcrashlogger/llcrashlogger.cpp
+++ b/indra/llcrashlogger/llcrashlogger.cpp
@@ -89,7 +89,8 @@ LLCrashLogger::LLCrashLogger() :
 	mSentCrashLogs(false),
 	mCrashHost("")
 {
-
+	// Set up generic error handling
+	setupErrorHandling();
 }
 
 LLCrashLogger::~LLCrashLogger()
diff --git a/indra/llinventory/llparcel.cpp b/indra/llinventory/llparcel.cpp
index 9c27476b0a..a0b27c788f 100644
--- a/indra/llinventory/llparcel.cpp
+++ b/indra/llinventory/llparcel.cpp
@@ -175,7 +175,7 @@ void LLParcel::init(const LLUUID &owner_id,
 	mSaleTimerExpires.stop();
 	mGraceExtension = 0;
 	//mExpireAction = STEA_REVERT;
-	mRecordTransaction = FALSE;
+	//mRecordTransaction = FALSE;
 
 	mAuctionID = 0;
 	mInEscrow = false;
diff --git a/indra/llinventory/llparcel.h b/indra/llinventory/llparcel.h
index 6f5ae87ebd..40bbb7b2e0 100644
--- a/indra/llinventory/llparcel.h
+++ b/indra/llinventory/llparcel.h
@@ -413,12 +413,6 @@ public:
 	void completeSale(U32& type, U8& flags, LLUUID& to_id);
 	void clearSale();
 
-	// this function returns TRUE if the parcel needs conversion to a
-	// lease from a non-owned-status state.
-	BOOL getRecordTransaction() const { return mRecordTransaction; }
-	void setRecordTransaction(BOOL record) { mRecordTransaction = record; }
-
-
 	// more accessors
 	U32		getParcelFlags() const			{ return mParcelFlags; }
 
@@ -596,8 +590,6 @@ protected:
 	ELandingType mLandingType;
 	LLTimer mSaleTimerExpires;
 	S32 mGraceExtension;
-	BOOL mRecordTransaction;
-	
 
 	// This value is non-zero if there is an auction associated with
 	// the parcel.
diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp
index 307d9b92fa..8b90a4c5ca 100644
--- a/indra/llmessage/llhttpclient.cpp
+++ b/indra/llmessage/llhttpclient.cpp
@@ -224,6 +224,10 @@ static void request(
 	LLURLRequest* req = new LLURLRequest(method, url);
 	req->checkRootCertificate(true);
 
+	
+	lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " "
+		<< headers << llendl;
+
     // Insert custom headers is the caller sent any
     if (headers.isMap())
     {
@@ -375,72 +379,140 @@ private:
 	std::string mBuffer;
 };
 
-// *TODO: Deprecate (only used by dataserver)
-// This call is blocking! This is probably usually bad. :(
-LLSD LLHTTPClient::blockingGet(const std::string& url)
+// These calls are blocking! This is usually bad, unless you're a dataserver. Then it's awesome.
+
+/**
+	@brief does a blocking request on the url, returning the data or bad status.
+
+	@param url URI to verb on.
+	@param method the verb to hit the URI with.
+	@param body the body of the call (if needed - for instance not used for GET and DELETE, but is for POST and PUT)
+	@param headers HTTP headers to use for the request.
+	@param timeout Curl timeout to use. Defaults to 5. Rationale:
+	Without this timeout, blockingGet() calls have been observed to take
+	up to 90 seconds to complete.  Users of blockingGet() already must 
+	check the HTTP return code for validity, so this will not introduce
+	new errors.  A 5 second timeout will succeed > 95% of the time (and 
+	probably > 99% of the time) based on my statistics. JC
+
+	@returns an LLSD map: {status: integer, body: map}
+  */
+static LLSD blocking_request(
+	const std::string& url,
+	LLURLRequest::ERequestAction method,
+	const LLSD& body,
+	const LLSD& headers = LLSD(),
+	const F32 timeout = 5
+)
 {
-	llinfos << "blockingGet of " << url << llendl;
-
-	// Returns an LLSD map: {status: integer, body: map}
-	char curl_error_buffer[CURL_ERROR_SIZE];
+	lldebugs << "blockingRequest of " << url << llendl;
+	char curl_error_buffer[CURL_ERROR_SIZE] = "\0";
 	CURL* curlp = curl_easy_init();
-
 	LLHTTPBuffer http_buffer;
-
-	// Without this timeout, blockingGet() calls have been observed to take
-	// up to 90 seconds to complete.  Users of blockingGet() already must 
-	// check the HTTP return code for validity, so this will not introduce
-	// new errors.  A 5 second timeout will succeed > 95% of the time (and 
-	// probably > 99% of the time) based on my statistics. JC
+	std::string body_str;
+	
+	// other request method checks root cert first, we skip?
+	//req->checkRootCertificate(true);
+	
+	// * Set curl handle options
 	curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1);	// don't use SIGALRM for timeouts
-	curl_easy_setopt(curlp, CURLOPT_TIMEOUT, 5);	// seconds
-
+	curl_easy_setopt(curlp, CURLOPT_TIMEOUT, timeout);	// seconds, see warning at top of function.
 	curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, LLHTTPBuffer::curl_write);
 	curl_easy_setopt(curlp, CURLOPT_WRITEDATA, &http_buffer);
 	curl_easy_setopt(curlp, CURLOPT_URL, url.c_str());
 	curl_easy_setopt(curlp, CURLOPT_ERRORBUFFER, curl_error_buffer);
-	curl_easy_setopt(curlp, CURLOPT_FAILONERROR, 1);
-
-	struct curl_slist *header_list = NULL;
-	header_list = curl_slist_append(header_list, "Accept: application/llsd+xml");
-	CURLcode curl_result = curl_easy_setopt(curlp, CURLOPT_HTTPHEADER, header_list);
+	
+	// * Setup headers (don't forget to free them after the call!)
+	curl_slist* headers_list = NULL;
+	if (headers.isMap())
+	{
+		LLSD::map_const_iterator iter = headers.beginMap();
+		LLSD::map_const_iterator end  = headers.endMap();
+		for (; iter != end; ++iter)
+		{
+			std::ostringstream header;
+			header << iter->first << ": " << iter->second.asString() ;
+			lldebugs << "header = " << header.str() << llendl;
+			headers_list = curl_slist_append(headers_list, header.str().c_str());
+		}
+	}
+	
+	// * Setup specific method / "verb" for the URI (currently only GET and POST supported + poppy)
+	if (method == LLURLRequest::HTTP_GET)
+	{
+		curl_easy_setopt(curlp, CURLOPT_HTTPGET, 1);
+	}
+	else if (method == LLURLRequest::HTTP_POST)
+	{
+		curl_easy_setopt(curlp, CURLOPT_POST, 1);
+		//serialize to ostr then copy to str - need to because ostr ptr is unstable :(
+		std::ostringstream ostr;
+		LLSDSerialize::toXML(body, ostr);
+		body_str = ostr.str();
+		curl_easy_setopt(curlp, CURLOPT_POSTFIELDS, body_str.c_str());
+		//copied from PHP libs, correct?
+		headers_list = curl_slist_append(headers_list, "Content-Type: application/llsd+xml");
+
+		// copied from llurlrequest.cpp
+		// it appears that apache2.2.3 or django in etch is busted. If
+		// we do not clear the expect header, we get a 500. May be
+		// limited to django/mod_wsgi.
+		headers_list = curl_slist_append(headers_list, "Expect:");
+	}
+	
+	// * Do the action using curl, handle results
+	lldebugs << "HTTP body: " << body_str << llendl;
+	headers_list = curl_slist_append(headers_list, "Accept: application/llsd+xml");
+	CURLcode curl_result = curl_easy_setopt(curlp, CURLOPT_HTTPHEADER, headers_list);
 	if ( curl_result != CURLE_OK )
 	{
-		llinfos << "Curl is hosed - can't add Accept header for llsd+xml" << llendl;
+		llinfos << "Curl is hosed - can't add headers" << llendl;
 	}
 
 	LLSD response = LLSD::emptyMap();
-
 	S32 curl_success = curl_easy_perform(curlp);
-
 	S32 http_status = 499;
-	curl_easy_getinfo(curlp,CURLINFO_RESPONSE_CODE, &http_status);
-
+	curl_easy_getinfo(curlp, CURLINFO_RESPONSE_CODE, &http_status);
 	response["status"] = http_status;
-
-	if (curl_success != 0 
-		&& http_status != 404)  // We expect 404s, don't spam for them.
+	// if we get a non-404 and it's not a 200 OR maybe it is but you have error bits,
+	if ( http_status != 404 && (http_status != 200 || curl_success != 0) )
 	{
+		// We expect 404s, don't spam for them.
+		llwarns << "CURL REQ URL: " << url << llendl;
+		llwarns << "CURL REQ METHOD TYPE: " << method << llendl;
+		llwarns << "CURL REQ HEADERS: " << headers.asString() << llendl;
+		llwarns << "CURL REQ BODY: " << body_str << llendl;
+		llwarns << "CURL HTTP_STATUS: " << http_status << llendl;
 		llwarns << "CURL ERROR: " << curl_error_buffer << llendl;
-		
+		llwarns << "CURL ERROR BODY: " << http_buffer.asString() << llendl;
 		response["body"] = http_buffer.asString();
 	}
 	else
 	{
 		response["body"] = http_buffer.asLLSD();
+		lldebugs << "CURL response: " << http_buffer.asString() << llendl;
 	}
 	
-	if(header_list)
+	if(headers_list)
 	{	// free the header list  
-		curl_slist_free_all(header_list); 
-		header_list = NULL;
+		curl_slist_free_all(headers_list); 
 	}
 
+	// * Cleanup
 	curl_easy_cleanup(curlp);
-
 	return response;
 }
 
+LLSD LLHTTPClient::blockingGet(const std::string& url)
+{
+	return blocking_request(url, LLURLRequest::HTTP_GET, LLSD());
+}
+
+LLSD LLHTTPClient::blockingPost(const std::string& url, const LLSD& body)
+{
+	return blocking_request(url, LLURLRequest::HTTP_POST, body);
+}
+
 void LLHTTPClient::put(
 	const std::string& url,
 	const LLSD& body,
diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h
index a0c9fac77f..3d0646e5fe 100644
--- a/indra/llmessage/llhttpclient.h
+++ b/indra/llmessage/llhttpclient.h
@@ -142,6 +142,14 @@ public:
 	 */
 	static LLSD blockingGet(const std::string& url);
 
+	/**
+	 * @brief Blocking HTTP POST that returns an LLSD map of status and body.
+	 *
+	 * @param url the complete serialized (and escaped) url to get
+	 * @param body the LLSD post body
+	 * @return An LLSD of { 'status':status (an int), 'body':payload (an LLSD) }
+	 */
+	static LLSD blockingPost(const std::string& url, const LLSD& body);
 
 	
 	static void setPump(LLPumpIO& pump);
diff --git a/indra/llmessage/llmessageconfig.cpp b/indra/llmessage/llmessageconfig.cpp
index d4279354b6..dff0a3844c 100644
--- a/indra/llmessage/llmessageconfig.cpp
+++ b/indra/llmessage/llmessageconfig.cpp
@@ -66,7 +66,7 @@ public:
 	static LLMessageConfigFile& instance();
 		// return the singleton configuration file
 
-	/* virtual */ void loadFile();
+	/* virtual */ bool loadFile();
 	void loadServerDefaults(const LLSD& data);
 	void loadMaxQueuedEvents(const LLSD& data);
 	void loadMessages(const LLSD& data);
@@ -98,7 +98,7 @@ LLMessageConfigFile& LLMessageConfigFile::instance()
 }
 
 // virtual
-void LLMessageConfigFile::loadFile()
+bool LLMessageConfigFile::loadFile()
 {
 	LLSD data;
     {
@@ -115,7 +115,7 @@ void LLMessageConfigFile::loadFile()
             LL_INFOS("AppInit") << "LLMessageConfigFile::loadFile: file missing,"
 				" ill-formed, or simply undefined; not changing the"
 				" file" << LL_ENDL;
-            return;
+            return false;
         }
     }
 	loadServerDefaults(data);
@@ -123,6 +123,7 @@ void LLMessageConfigFile::loadFile()
 	loadMessages(data);
 	loadCapBans(data);
 	loadMessageBans(data);
+	return true;
 }
 
 void LLMessageConfigFile::loadServerDefaults(const LLSD& data)
diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp
index 46e976fe35..3ab8057abb 100644
--- a/indra/llmessage/llurlrequest.cpp
+++ b/indra/llmessage/llurlrequest.cpp
@@ -98,6 +98,26 @@ LLURLRequestDetail::~LLURLRequestDetail()
  * class LLURLRequest
  */
 
+// static
+std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
+{
+	static const std::string VERBS[] =
+	{
+		"(invalid)",
+		"HEAD",
+		"GET",
+		"PUT",
+		"POST",
+		"DELETE",
+		"MOVE"
+	};
+	if(((S32)action <=0) || ((S32)action >= REQUEST_ACTION_COUNT))
+	{
+		return VERBS[0];
+	}
+	return VERBS[action];
+}
+
 LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) :
 	mAction(action)
 {
diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h
index d1facbff0f..86ef71f085 100644
--- a/indra/llmessage/llurlrequest.h
+++ b/indra/llmessage/llurlrequest.h
@@ -81,6 +81,11 @@ public:
 		REQUEST_ACTION_COUNT
 	};
 
+	/**
+	 * @brief Turn the requst action into an http verb.
+	 */
+	static std::string actionAsVerb(ERequestAction action);
+
 	/** 
 	 * @brief Constructor.
 	 *
diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h
index b25b27eb0f..0f3576732d 100644
--- a/indra/llmessage/message.h
+++ b/indra/llmessage/message.h
@@ -509,6 +509,22 @@ private:
 public:
 	// BOOL	decodeData(const U8 *buffer, const LLHost &host);
 
+	/**
+	gets binary data from the current message.
+	
+	@param blockname the name of the block in the message (from the message template)
+
+	@param varname 
+
+	@param datap
+	
+	@param size expected size - set to zero to get any amount of data up to max_size.
+	Make sure max_size is set in that case!
+
+	@param blocknum
+
+	@param max_size the max number of bytes to read
+	*/
 	void	getBinaryDataFast(const char *blockname, const char *varname, void *datap, S32 size, S32 blocknum = 0, S32 max_size = S32_MAX);
 	void	getBinaryData(const char *blockname, const char *varname, void *datap, S32 size, S32 blocknum = 0, S32 max_size = S32_MAX);
 	void	getBOOLFast(	const char *block, const char *var, BOOL &data, S32 blocknum = 0);
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index f2154a05dc..640b835da2 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -523,6 +523,7 @@ LLAppViewer::LLAppViewer() :
 		llerrs << "Oh no! An instance of LLAppViewer already exists! LLAppViewer is sort of like a singleton." << llendl;
 	}
 
+	setupErrorHandling();
 	sInstance = this;
 }
 
-- 
cgit v1.2.3