/* Copyright information is at end of file */

/* Implementation note:

   The printf format specifiers we use appear to be entirely standard,
   except for the "long long" one, which is %I64 on Windows and %lld
   everywhere else.  So for that, we use the C99 standard macro PRId64,
   which is defined by inttypes.h.  Ironically, Windows doesn't have
   inttypes.h either, but we have int.h instead.
*/

#include "xmlrpc_config.h"

#include <assert.h>
#include <stddef.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <float.h>

#include "int.h"
#include "xmlrpc-c/base.h"
#include "xmlrpc-c/base_int.h"
#include "xmlrpc-c/string_int.h"
#include "double.h"

#define CRLF "\015\012"
#define XML_PROLOGUE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"CRLF
#define APACHE_URL "http://ws.apache.org/xmlrpc/namespaces/extensions"
#define XMLNS_APACHE "xmlns:ex=\"" APACHE_URL "\""


static void
addString(xmlrpc_env *       const envP,
          xmlrpc_mem_block * const outputP,
          const char *       const string) {

    XMLRPC_MEMBLOCK_APPEND(char, envP, outputP, string, strlen(string));
}



static void 
formatOut(xmlrpc_env *       const envP,
          xmlrpc_mem_block * const outputP,
          const char *       const formatString,
          ...) {
/*----------------------------------------------------------------------------
  A lightweight print routine for use with various serialization
  functions.

  Use this routine only for printing small objects -- it uses a
  fixed-size internal buffer and returns an error on overflow.  In
  particular, do NOT use this routine to print XML-RPC string values!
-----------------------------------------------------------------------------*/
    va_list args;
    char buffer[128];
    int rc;

    XMLRPC_ASSERT_ENV_OK(envP);

    va_start(args, formatString);

    rc = XMLRPC_VSNPRINTF(buffer, sizeof(buffer), formatString, args);

    /* Old vsnprintf() (and Windows) fails with return value -1 if the full
       string doesn't fit in the buffer.  New vsnprintf() puts whatever will
       fit in the buffer, and returns the length of the full string
       regardless.  For us, this truncation is a failure.
    */

    if (rc < 0)
        xmlrpc_faultf(envP, "formatOut() overflowed internal buffer");
    else {
        unsigned int const formattedLen = rc;

        if (formattedLen + 1 >= (sizeof(buffer)))
            xmlrpc_faultf(envP, "formatOut() overflowed internal buffer");
        else
            XMLRPC_MEMBLOCK_APPEND(char, envP, outputP, buffer, formattedLen);
    }
    va_end(args);
}



static void 
assertValidUtf8(const char * const str ATTR_UNUSED,
                size_t       const len ATTR_UNUSED) {
/*----------------------------------------------------------------------------
   Assert that the string 'str' of length 'len' is valid UTF-8.
-----------------------------------------------------------------------------*/
#if !defined NDEBUG
    /* Check the assertion; if it's false, issue a message to
       Standard Error, but otherwise ignore it.
    */
    xmlrpc_env env;

    xmlrpc_env_init(&env);
    xmlrpc_validate_utf8(&env, str, len);
    if (env.fault_occurred)
        fprintf(stderr, "*** xmlrpc-c WARNING ***: %s (%s)\n",
                "Xmlrpc-c sending corrupted UTF-8 data to network",
                env.fault_string);
    xmlrpc_env_clean(&env);
#endif
}



static size_t
escapedSize(const char * const chars,
            size_t       const len) {
    
    size_t size;
    size_t i;

    size = 0;
    for (i = 0; i < len; ++i) {
        if (chars[i] == '<')
            size += 4; /* &lt; */
        else if (chars[i] == '>')
            size += 4; /* &gt; */
        else if (chars[i] == '&')
            size += 5; /* &amp; */
        else if (chars[i] == '\r')
            size += 6; /* &#x0d; */
        else
            size += 1;
    }
    return size;
}



static void
escapeForXml(xmlrpc_env *        const envP, 
             const char *        const chars,
             size_t              const len,
             xmlrpc_mem_block ** const outputPP) {
/*----------------------------------------------------------------------------
   Escape & and < in a UTF-8 string so as to make it suitable for the
   content of an XML element.  I.e. turn them into entity references
   &amp; and &lt;.

   Also change > to &gt;, even though not required for XML, for
   symmetry.

   &lt; etc. are known in XML as "entity references."
   
   Also Escape CR as &#x0d; .  While raw CR _is_ allowed in the content
   of an XML element, it has a special meaning -- it means line ending.
   Our input uses LF for for line endings.  Since it also means line ending
   in XML, we just pass it through to our output like it were a regular
   character.

   &#x0d; is known in XML as a "character reference."

   We assume chars[] is is ASCII.  That isn't right -- we should
   handle all valid UTF-8.  Someday, we must do something more complex
   and copy over multibyte characters verbatim.  (The code here could
   erroneously find that e.g. the 2nd byte of a UTF-8 character is a
   CR).
-----------------------------------------------------------------------------*/
    xmlrpc_mem_block * outputP;
    size_t outputSize;

    XMLRPC_ASSERT_ENV_OK(envP);
    XMLRPC_ASSERT(chars != NULL);

    assertValidUtf8(chars, len);

    /* Note that in UTF-8, any byte that has high bit of zero is a
       character all by itself (every byte of a multi-byte UTF-8 character
       has the high bit set).  Also, the Unicode code points < 128 are
       identical to the ASCII ones.
    */

    outputSize = escapedSize(chars, len);

    outputP = XMLRPC_MEMBLOCK_NEW(char, envP, outputSize);
    if (!envP->fault_occurred) {
        char * p;
        size_t i;
        p = XMLRPC_MEMBLOCK_CONTENTS(char, outputP); /* Start at beginning */

        for (i = 0; i < len; i++) {
            if (chars[i] == '<') {
                memcpy(p, "&lt;", 4);
                p += 4;
            } else if (chars[i] == '>') {
                memcpy(p, "&gt;", 4);
                p += 4;
            } else if (chars[i] == '&') {
                memcpy(p, "&amp;", 5);
                p += 5;
            } else if (chars[i] == '\r') {
                memcpy(p, "&#x0d;", 6);
                p += 6;
            } else {
                /* Either a plain character or a LF line delimiter */
                *p = chars[i];
                p += 1;
            }
        }
        *outputPP = outputP;
        assert(p == XMLRPC_MEMBLOCK_CONTENTS(char, outputP) + outputSize);

        if (envP->fault_occurred)
            XMLRPC_MEMBLOCK_FREE(char, outputP);
    }
}



static void 
serializeUtf8MemBlock(xmlrpc_env *       const envP,
                      xmlrpc_mem_block * const outputP,
                      xmlrpc_mem_block * const inputP) {
/*----------------------------------------------------------------------------
   Append the characters in *inputP to the XML stream in *outputP.

   *inputP contains Unicode characters in UTF-8.

   We assume *inputP ends with a NUL character that marks end of
   string, and we ignore that.  (There might also be NUL characters
   inside the string, though).
-----------------------------------------------------------------------------*/
    xmlrpc_mem_block * escapedP;

    XMLRPC_ASSERT_ENV_OK(envP);
    XMLRPC_ASSERT(outputP != NULL);
    XMLRPC_ASSERT(inputP != NULL);

    escapeForXml(envP,
                 XMLRPC_MEMBLOCK_CONTENTS(const char, inputP),
                 XMLRPC_MEMBLOCK_SIZE(const char, inputP) - 1,
                    /* -1 is for the terminating NUL */
                 &escapedP);
    if (!envP->fault_occurred) {
        const char * const contents =
            XMLRPC_MEMBLOCK_CONTENTS(const char, escapedP);
        size_t const size = XMLRPC_MEMBLOCK_SIZE(char, escapedP);
    
        XMLRPC_MEMBLOCK_APPEND(char, envP, outputP, contents, size);

        XMLRPC_MEMBLOCK_FREE(const char, escapedP);
    }
}



static void 
xmlrpc_serialize_base64_data(xmlrpc_env *       const envP,
                             xmlrpc_mem_block * const output,
                             unsigned char *    const data, 
                             size_t             const len) {
/*----------------------------------------------------------------------------
   Encode the 'len' bytes at 'data' in base64 ASCII and append the result to
   'output'.
-----------------------------------------------------------------------------*/
    xmlrpc_mem_block * encoded;

    encoded = xmlrpc_base64_encode(envP, data, len);
    if (!envP->fault_occurred) {
        unsigned char * const contents =
            XMLRPC_MEMBLOCK_CONTENTS(unsigned char, encoded);
        size_t const size = 
            XMLRPC_MEMBLOCK_SIZE(unsigned char, encoded);
        
        XMLRPC_MEMBLOCK_APPEND(char, envP, output, contents, size);
        
        XMLRPC_MEMBLOCK_FREE(char, encoded);
    }
}



static void 
serializeStruct(xmlrpc_env *       const envP,
                xmlrpc_mem_block * const outputP,
                xmlrpc_value *     const structP,
                xmlrpc_dialect     const dialect) {
/*----------------------------------------------------------------------------
   Add to *outputP the content of a <value> element to represent
   the structure value *valueP.  I.e. "<struct> ... </struct>".
-----------------------------------------------------------------------------*/
    size_t size;
    size_t i;
    xmlrpc_value * memberKeyP;
    xmlrpc_value * memberValueP;

    addString(envP, outputP, "<struct>"CRLF);
    XMLRPC_FAIL_IF_FAULT(envP);

    size = xmlrpc_struct_size(envP, structP);
    XMLRPC_FAIL_IF_FAULT(envP);
    for (i = 0; i < size; ++i) {
        xmlrpc_struct_get_key_and_value(envP, structP, i,
                                        &memberKeyP, &memberValueP);
        XMLRPC_FAIL_IF_FAULT(envP);
        addString(envP, outputP, "<member><name>");
        XMLRPC_FAIL_IF_FAULT(envP);
        serializeUtf8MemBlock(envP, outputP, &memberKeyP->_block);
        XMLRPC_FAIL_IF_FAULT(envP);
        addString(envP, outputP, "</name>"CRLF);
        XMLRPC_FAIL_IF_FAULT(envP);
        xmlrpc_serialize_value2(envP, outputP, memberValueP, dialect);
        XMLRPC_FAIL_IF_FAULT(envP);
        addString(envP, outputP, "</member>"CRLF);
        XMLRPC_FAIL_IF_FAULT(envP);
    }

    addString(envP, outputP, "</struct>");
    XMLRPC_FAIL_IF_FAULT(envP);

cleanup:
    return;
}



static void
serializeArray(xmlrpc_env *       const envP,
               xmlrpc_mem_block * const outputP,
               xmlrpc_value *     const valueP,
               xmlrpc_dialect     const dialect) {
/*----------------------------------------------------------------------------
   Add to *outputP the content of a <value> element to represent
   the array value *valueP.  I.e. "<array> ... </array>".
-----------------------------------------------------------------------------*/
    int const size = xmlrpc_array_size(envP, valueP);

    if (!envP->fault_occurred) {
        addString(envP, outputP, "<array><data>"CRLF);
        if (!envP->fault_occurred) {
            int i;
            /* Serialize each item. */
            for (i = 0; i < size && !envP->fault_occurred; ++i) {
                xmlrpc_value * const itemP =
                    xmlrpc_array_get_item(envP, valueP, i);
                if (!envP->fault_occurred) {
                    xmlrpc_serialize_value2(envP, outputP, itemP, dialect);
                    if (!envP->fault_occurred)
                        addString(envP, outputP, CRLF);
                }
            }
        }
    }
    if (!envP->fault_occurred)
        addString(envP, outputP, "</data></array>");
} 



static void
formatValueContent(xmlrpc_env *       const envP,
                   xmlrpc_mem_block * const outputP,
                   xmlrpc_value *     const valueP,
                   xmlrpc_dialect     const dialect) {
/*----------------------------------------------------------------------------
   Add to *outputP the content of a <value> element to represent
   value *valueP.  E.g. "<int>42</int>"
-----------------------------------------------------------------------------*/
    XMLRPC_ASSERT_ENV_OK(envP);

    switch (valueP->_type) {
    case XMLRPC_TYPE_INT:
        formatOut(envP, outputP, "<i4>%d</i4>", valueP->_value.i);
        break;

    case XMLRPC_TYPE_I8: {
        const char * const elemName =
            dialect == xmlrpc_dialect_apache ? "ex:i8" : "i8";
        formatOut(envP, outputP, "<%s>%" PRId64 "</%s>",
                  elemName, valueP->_value.i8, elemName);
    } break;

    case XMLRPC_TYPE_BOOL:
        formatOut(envP, outputP, "<boolean>%s</boolean>",
                  valueP->_value.b ? "1" : "0");
        break;

    case XMLRPC_TYPE_DOUBLE: {
        const char * serializedValue;
        xmlrpc_formatFloat(envP, valueP->_value.d, &serializedValue);
        if (!envP->fault_occurred) {
            addString(envP, outputP, "<double>");
            if (!envP->fault_occurred) {
                addString(envP, outputP, serializedValue);
                if (!envP->fault_occurred)
                    addString(envP, outputP, "</double>");
            }
            xmlrpc_strfree(serializedValue);
        }
    } break;

    case XMLRPC_TYPE_DATETIME:
        addString(envP, outputP, "<dateTime.iso8601>");
        if (!envP->fault_occurred) {
            serializeUtf8MemBlock(envP, outputP, &valueP->_block);
            if (!envP->fault_occurred) {
                addString(envP, outputP, "</dateTime.iso8601>");
            }
        }
        break;

    case XMLRPC_TYPE_STRING:
        addString(envP, outputP, "<string>");
        if (!envP->fault_occurred) {
            serializeUtf8MemBlock(envP, outputP, &valueP->_block);
            if (!envP->fault_occurred)
                addString(envP, outputP, "</string>");
        }
        break;

    case XMLRPC_TYPE_BASE64: {
        unsigned char * const contents =
            XMLRPC_MEMBLOCK_CONTENTS(unsigned char, &valueP->_block);
        size_t const size =
            XMLRPC_MEMBLOCK_SIZE(unsigned char, &valueP->_block);
        addString(envP, outputP, "<base64>"CRLF);
        if (!envP->fault_occurred) {
            xmlrpc_serialize_base64_data(envP, outputP, contents, size);
            if (!envP->fault_occurred)
                addString(envP, outputP, "</base64>");
        }
    } break;      

    case XMLRPC_TYPE_ARRAY:
        serializeArray(envP, outputP, valueP, dialect);
        break;

    case XMLRPC_TYPE_STRUCT:
        serializeStruct(envP, outputP, valueP, dialect);
        break;

    case XMLRPC_TYPE_C_PTR:
        xmlrpc_faultf(envP, "Tried to serialize a C pointer value.");
        break;

    case XMLRPC_TYPE_NIL: {
        const char * const elemName =
            dialect == xmlrpc_dialect_apache ? "ex:nil" : "nil";
        formatOut(envP, outputP, "<%s/>", elemName);
    } break;

    case XMLRPC_TYPE_DEAD:
        xmlrpc_faultf(envP, "Tried to serialize a dead value.");
        break;

    default:
        xmlrpc_faultf(envP, "Invalid xmlrpc_value type: %d", valueP->_type);
    }
}



void 
xmlrpc_serialize_value2(xmlrpc_env *       const envP,
                        xmlrpc_mem_block * const outputP,
                        xmlrpc_value *     const valueP,
                        xmlrpc_dialect     const dialect) {
/*----------------------------------------------------------------------------
   Generate the XML to represent XML-RPC value 'valueP' in XML-RPC.

   Add it to *outputP.
-----------------------------------------------------------------------------*/
    XMLRPC_ASSERT_ENV_OK(envP);
    XMLRPC_ASSERT(outputP != NULL);
    XMLRPC_ASSERT_VALUE_OK(valueP);

    addString(envP, outputP, "<value>");

    if (!envP->fault_occurred) {
        formatValueContent(envP, outputP, valueP, dialect);

        if (!envP->fault_occurred)
            addString(envP, outputP, "</value>");
    }
}



void 
xmlrpc_serialize_value(xmlrpc_env *       const envP,
                       xmlrpc_mem_block * const outputP,
                       xmlrpc_value *     const valueP) {

    xmlrpc_serialize_value2(envP, outputP, valueP, xmlrpc_dialect_i8);
}



void 
xmlrpc_serialize_params2(xmlrpc_env *       const envP,
                         xmlrpc_mem_block * const outputP,
                         xmlrpc_value *     const paramArrayP,
                         xmlrpc_dialect     const dialect) {
/*----------------------------------------------------------------------------
   Serialize the parameter list of an XML-RPC call.
-----------------------------------------------------------------------------*/
    XMLRPC_ASSERT_ENV_OK(envP);
    XMLRPC_ASSERT(outputP != NULL);
    XMLRPC_ASSERT_VALUE_OK(paramArrayP);

    addString(envP, outputP, "<params>"CRLF);
    if (!envP->fault_occurred) {
        /* Serialize each parameter. */
        size_t const paramCount = xmlrpc_array_size(envP, paramArrayP);
        if (!envP->fault_occurred) {
            size_t paramSeq;
            for (paramSeq = 0;
                 paramSeq < paramCount && !envP->fault_occurred;
                 ++paramSeq) {

                addString(envP, outputP, "<param>");
                if (!envP->fault_occurred) {
                    xmlrpc_value * const itemP =
                        xmlrpc_array_get_item(envP, paramArrayP, paramSeq);
                    if (!envP->fault_occurred) {
                        xmlrpc_serialize_value2(envP, outputP, itemP, dialect);
                        if (!envP->fault_occurred)
                            addString(envP, outputP, "</param>"CRLF);
                    }
                }
            }
        }
    }

    if (!envP->fault_occurred)
        addString(envP, outputP, "</params>"CRLF);
}



void 
xmlrpc_serialize_params(xmlrpc_env *       const envP,
                        xmlrpc_mem_block * const outputP,
                        xmlrpc_value *     const paramArrayP) {
/*----------------------------------------------------------------------------
   Serialize the parameter list of an XML-RPC call in the original
   "i8" dialect.
-----------------------------------------------------------------------------*/
    xmlrpc_serialize_params2(envP, outputP, paramArrayP, xmlrpc_dialect_i8);
}



/*=========================================================================
**  xmlrpc_serialize_call
**=========================================================================
**  Serialize an XML-RPC call.
*/                

void 
xmlrpc_serialize_call2(xmlrpc_env *       const envP,
                       xmlrpc_mem_block * const outputP,
                       const char *       const methodName,
                       xmlrpc_value *     const paramArrayP,
                       xmlrpc_dialect     const dialect) {
/*----------------------------------------------------------------------------
   Serialize an XML-RPC call of method named 'methodName' with parameter
   list *paramArrayP.  Use XML-RPC dialect 'dialect'.

   Append the call XML ot *outputP.
-----------------------------------------------------------------------------*/
    XMLRPC_ASSERT_ENV_OK(envP);
    XMLRPC_ASSERT(outputP != NULL);
    XMLRPC_ASSERT(methodName != NULL);
    XMLRPC_ASSERT_VALUE_OK(paramArrayP);
    
    addString(envP, outputP, XML_PROLOGUE);
    if (!envP->fault_occurred) {
        const char * const xmlns =
            dialect == xmlrpc_dialect_apache ? " " XMLNS_APACHE : "";
        formatOut(envP, outputP, "<methodCall%s>"CRLF"<methodName>", xmlns);
        if (!envP->fault_occurred) {
            xmlrpc_mem_block * encodedP;
            escapeForXml(envP, methodName, strlen(methodName), &encodedP);
            if (!envP->fault_occurred) {
                const char * const contents =
                    XMLRPC_MEMBLOCK_CONTENTS(char, encodedP);
                size_t const size = XMLRPC_MEMBLOCK_SIZE(char, encodedP);
                XMLRPC_MEMBLOCK_APPEND(char, envP, outputP, contents, size);
                if (!envP->fault_occurred) {
                    addString(envP, outputP, "</methodName>"CRLF);
                    if (!envP->fault_occurred) {
                        xmlrpc_serialize_params2(envP, outputP, paramArrayP,
                                                 dialect);
                        if (!envP->fault_occurred)
                            addString(envP, outputP, "</methodCall>"CRLF);
                    }
                }
                XMLRPC_MEMBLOCK_FREE(char, encodedP);
            }
        }
    }
}



void 
xmlrpc_serialize_call(xmlrpc_env *       const envP,
                      xmlrpc_mem_block * const outputP,
                      const char *       const methodName,
                      xmlrpc_value *     const paramArrayP) {

    xmlrpc_serialize_call2(envP, outputP, methodName, paramArrayP,
                           xmlrpc_dialect_i8);
}



void 
xmlrpc_serialize_response2(xmlrpc_env *       const envP,
                           xmlrpc_mem_block * const outputP,
                           xmlrpc_value *     const valueP,
                           xmlrpc_dialect     const dialect) {
/*----------------------------------------------------------------------------
  Serialize a result response to an XML-RPC call.

  The result is 'valueP'.

  Add the response XML to *outputP.
-----------------------------------------------------------------------------*/
    XMLRPC_ASSERT_ENV_OK(envP);
    XMLRPC_ASSERT(outputP != NULL);
    XMLRPC_ASSERT_VALUE_OK(valueP);

    addString(envP, outputP, XML_PROLOGUE);
    if (!envP->fault_occurred) {
        const char * const xmlns =
            dialect == xmlrpc_dialect_apache ? " " XMLNS_APACHE : "";
        formatOut(envP, outputP,
                  "<methodResponse%s>"CRLF"<params>"CRLF"<param>", xmlns);
        if (!envP->fault_occurred) {
            xmlrpc_serialize_value2(envP, outputP, valueP, dialect);
            if (!envP->fault_occurred) {
                addString(envP, outputP,
                          "</param>"CRLF"</params>"CRLF
                          "</methodResponse>"CRLF);
            }
        }
    }    
}



void 
xmlrpc_serialize_response(xmlrpc_env *       const envP,
                          xmlrpc_mem_block * const outputP,
                          xmlrpc_value *     const valueP) {

    xmlrpc_serialize_response2(envP, outputP, valueP, xmlrpc_dialect_i8);
}



void 
xmlrpc_serialize_fault(xmlrpc_env *       const envP,
                       xmlrpc_mem_block * const outputP,
                       const xmlrpc_env * const faultP) {
/*----------------------------------------------------------------------------
   Serialize a fault response to an XML-RPC call.

   'faultP' is the fault.

   Add the response XML to *outputP.
-----------------------------------------------------------------------------*/
    xmlrpc_value * faultStructP;

    XMLRPC_ASSERT_ENV_OK(envP);
    XMLRPC_ASSERT(outputP != NULL);
    XMLRPC_ASSERT(faultP != NULL);
    XMLRPC_ASSERT(faultP->fault_occurred);

    faultStructP = xmlrpc_build_value(envP, "{s:i,s:s}",
                                      "faultCode",
                                      (xmlrpc_int32) faultP->fault_code,
                                      "faultString", faultP->fault_string);
    if (!envP->fault_occurred) {
        addString(envP, outputP, XML_PROLOGUE);
        if (!envP->fault_occurred) {
            addString(envP, outputP, "<methodResponse>"CRLF"<fault>"CRLF);
            if (!envP->fault_occurred) {
                xmlrpc_serialize_value(envP, outputP, faultStructP);
                if (!envP->fault_occurred) {
                    addString(envP, outputP,
                              CRLF"</fault>"CRLF"</methodResponse>"CRLF);
                }
            }
        }
        xmlrpc_DECREF(faultStructP);
    }
}



/* Copyright (C) 2001 by First Peer, Inc. All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission. 
**  
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE. */
