#pragma ident "$Header: /san01/cvs/ashpool/csrc/moddynamo/Attic/hook.c,v 1.12 2004/03/18 15:04:15 aleigh Exp $"

/*
 * Copyright (c) 2003 Alex Leigh, All Rights Reserved
 * 
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 * 
 * Disclosure, copying, distribution, or transmission of of this code without
 * express permission is prohibited.
 */

#include "DRPClient.h"
#include "CMLDRP.h"
#include "cmVersion.h"

#include <continuity.h>
#include <http.h>
#include "../continuity/mecha/lst.h"

#undef DEBUG

/** SHELL CALLBACK FUNCTION TYPE DEFINITIONS **/
/*
 * these prototypes indicate the callback interface used by the DRP2 client
 * to access functionality in the CM "shell" specific to each server API
 */

/*
 * pBlock->logFunction(pBlock->logShellData, kLog_Error,
 * kLogMessage_CantResolveCMLDRPHost, iServers[s].host, NULL); }
 */

static const char *sLogMessages[] = {
    NULL,

    /* server-api-independent messages */
    "mod/dynamo: Failure while attempting to use DRP server at %s:%s to service a request.",
    "mod/dynamo: Could not find a functioning DRP server to service a request.",
    "mod/dynamo: Cannot create socket for connection to DRP server; error number is: %s.",
    "mod/dynamo: Cannot connect to DRP server; error number is: %s.",
    "mod/dynamo: Internal protocol error: unexpected DRP packet type: %s.",
    "mod/dynamo: Bad character found in Dynamo3 session identifier: %s.",
    "mod/dynamo: CMLDRP subsystem cannot start: cannot create read-write lock.",
    "mod/dynamo: CMLDRP subsystem cannot start: cannot start updater thread.",
    "mod/dynamo: Bad CMLDRP packet response type received from CMLDRP server at %s:%s.",
    "mod/dynamo: No response received from CMLDRP server at %s:%s for 150 seconds.",
    "mod/dynamo: No response received from CMLDRP server at %s:%s.",
    "mod/dynamo: %s",
    "mod/dynamo: Cannot resolve the CMLDRP server host: %s.",
    "mod/dynamo: Cannot continue; no acceptable CMLDRP servers specified.",
    NULL
};

void logHook(void *shell, int log_level, int log_message, char *arg1,
             char *arg2)
{
    int status;

    switch (log_level) {
    case kLog_Info:
        status = 0;
        break;
    case kLog_Warn:
        status = 1;
        break;
    case kLog_Error:
        status = 2;
        break;
    default:
        status = 3;
    }

    logFmsg(status, sLogMessages[log_message], arg1, arg2);
}

/**
 * GetParameter_fn
 * Retrieve the value (in ascii) for a request parameter (e.g. URI,
 *   server port, etc).
 * pShellData: shell-specific (request-specific) data object
 * iName: incdicates parameter to retrieve
 * oVal: (growable) buffer in which to store parameter value (in ascii)
 *       (if no value found, return successfully with empty oVal buffer)
 * return: 0 == success; !0 == failure (other than val not found)
 */

int GetParameter(void *pShellData, const drpIdentifier * iName,
                 growBuffer * oVal)
{
    httpTtrans *t = (httpTtrans *) pShellData;
    char *value = NULL;
    int returnValue = 0;

    oVal->used = 0;
    oVal->buffer[0] = kChar_Null;

    if (iName->name.type == kDefined) {
        drpByte defined = iName->name.value.defined;

        switch (defined) {
        case kProtocol:
            value = lstFset_get(t->vars, "protocol");
            break;

        case kServerName:      /* HOSTNAME */
            value = "10.10.10.22";
            break;

        case kMethod:
            value = lstFset_get(t->vars, "method");
            break;

        case kContentType:
            value = lstFset_get(t->req_hdrs, "content-type");
            break;

        case kUserAgent:
            value = lstFset_get(t->req_hdrs, "user-agent");
            break;

        case kContentLength:
            value = lstFset_get(t->req_hdrs, "content-length");
            break;

        case kRequestURI:
            /* request URI: parsed URI, no dynamo args or query args. */
            value = lstFset_get(t->vars, "uri");
            break;

        case kQueryString:
            value = lstFset_get(t->vars, "query");
            break;

        case kPathInfo:
            /* path-iinfo: path_info + dynamo-args */
            value = lstFset_get(t->vars, "req_uri");
            break;

        case kRemoteAddr:
            value = lstFset_get(t->vars, "remote_addr");
            if (value == NULL) {
                char remote_addr[16];
                utlFip_to_str(t->cli_ipv4_addr, remote_addr, 16);
                lstFset_add(t->vars, "remote_addr", remote_addr);
                value = lstFset_get(t->vars, "remote_addr");
            }
            break;

        case kRemoteHost:
            value = lstFset_get(t->vars, "remote_addr");
            if (value == NULL) {
                char remote_addr[16];
                utlFip_to_str(t->cli_ipv4_addr, remote_addr, 16);
                lstFset_add(t->vars, "remote_addr", remote_addr);
                value = lstFset_get(t->vars, "remote_addr");
            }
            break;

        case kAuthType:
            break;

        case kRemoteUser:

            break;

        case kPathTranslated:
            /*
             * This should be the full path to the jhtml file on the dyanmo app
             * server. If this is NULL, the dynamo server will try to work out
             * the path from the request uri and it's idea of the document root,
             * if it's configured to do so.
             */
            value = lstFset_get(t->vars, "path");
            break;

        case kServerPort:
            value = "8990";
            break;

        case kServletPath:

            break;

        case kScheme:
            /* This is used by dynamo for building redirects. */
            value = "http";
            break;

        default:
            break;

        }
    } else {
#ifdef DEBUG
        logFmsg(3, "mod/dynamo: getParameter: Called for undefined.");
#endif
    }

    if (value) {
        concatenate(oVal, value);
        return returnValue;
    } else {
        return 0;
    }
}

/**
 * GetNthHeader_fn
 * Retrieve the name and value (in ascii) of the nth request header.
 * pShellData: shell-specific (request-specific) data object
 * iHeaderNum: 0-based index of header to retrieve
 * oName, oVal: (growable) buffers in which to store header name and value
 *        (in ascii) (if no header for iHeaderNum, return successfully
 *        with empty name and val buffers)
 * return: 0 == success; !0 == failure (other than no such header)
 */
int GetNthHeader(void *pShellData, int iHeaderNum, growBuffer * oName,
                 growBuffer * oVal)
{
    httpTtrans *t = (httpTtrans *) pShellData;
    int index;

    index = lstFnth_find(t->req_hdrs, iHeaderNum);

    oName->used = 0;
    oName->buffer[0] = kChar_Null;
    oVal->used = 0;
    oVal->buffer[0] = kChar_Null;

    if (index == -1) {
        return 0;
    }
    concatenate(oName, t->req_hdrs->fields[index].name);
    concatenate(oVal, t->req_hdrs->fields[index].value);

    return 0;
}


/**
 * ReadFromAgent_fn
 * Read at most iMaxLength bytes from the user agent (browser).
 * pShellData: shell-specific (request-specific) data object
 * oBuffer: buffer in which to store data read from browser; this callback
 *          should not increase the size available in oBuffer
 * iMaxLength: maximum number of bytes to read from agent; actual number of
 *             bytes read is indicated via oBuffer->used.  guaranteed to be
 *             <= bytes available in oBuffer.
 * return: 0 == success (including EOF); !0 == failure (other than EOF)
 * note: if the data stream to the agent has been closed (physically or
 *       logically), return failure.
 */
int ReadFromAgent(void *pShellData, growBuffer * oBuffer,
                  size_t iMaxLength)
{
    httpTtrans *t = (httpTtrans *) pShellData;
    char *clen;
    int read;

    if (t->client_read == 0) {
        clen = lstFset_get(t->req_hdrs, "content-length");
        if (clen == NULL)
            return 0;
        t->client_read = atoi(clen);
    }
    if (t->client_read == -1)
        return 0;

    if (iMaxLength < t->client_read)
        read = httpFread(t, oBuffer->buffer, iMaxLength);
    else
        read = httpFread(t, oBuffer->buffer, t->client_read);


    if (read < 1)
        return -1;

    oBuffer->used = read;

    t->client_read -= read;
    if (t->client_read == 0)
        t->client_read = -1;

    return 0;
}

/**
 * WriteToAgent_fn
 * Write iLength bytes to the user agent (browser).
 * pShellData: shell-specific (request-specific) data object
 * iBuffer: contains data to write to agent
 * iLength: number of bytes to write to agent.
 * return: 0 == success (all bytes were written); !0 == failure
 * note: if the data stream to the agent has been closed (physically or
 *       logically), return failure.
 */
int WriteToAgent
    (void *pShellData, const growBuffer * iBuffer, size_t iLength) {
    httpTtrans *t = (httpTtrans *) pShellData;

    if (httpFwrite(t, iBuffer->buffer, iLength) != iLength)
        return -1;

    return 0;
}

/**
 * FlushData_fn
 * Flush the data stream to the user agent (browser), if possible.
 * pShellData: shell-specific (request-specific) data object
 * note: if the data stream to the agent has been closed (physically or
 *       logically), return failure.
 */
int FlushData(void *pShellData) {
    httpTtrans *t = (httpTtrans *) pShellData;

    /*
     * Flushing is not required for continuity, since writes aren't buffered
     */

    return 0;

}

/**
 * CloseData_fn
 * Close the data stream to the user agent (browser), if possible.
 * pShellData: shell-specific (request-specific) data object
 * note: if it is not possible or advisable for the CM shell to close the
 *       data stream, it is permissible to mark it as closed for the
 *       purpose of responding to future callbacks as if the stream were
 *       closed.
 */
int CloseData(void *pShellData) {
    httpTtrans *t = (httpTtrans *) pShellData;

    /* Shouldn't do this under Continuity.. Just pretend. */
    /* close(t->sd); */

    return 0;
}

/**
 * SendHeaderBatch_fn
 * Send HTTP response status and header(s) to user agent (browser);
 *   headers are provided in a preformatted block.
 * pShellData: shell-specific (request-specific) data object
 * iStatusCode: HTTP status code (e.g. 200)
 * iStatusMesg: optional status message for HTTP status line
 * iMesgLength: number of relevant bytes in iStatusMesg; test iMesgLength
 *              rather than iStatusMesg for presence of message
 * iHeaders: preformatted block of header name-value pairs, including
 *           newlines (should need no parsing by shell)
 * iHeadersLength: number of relevant bytes in iHeaders
 * return: 0 == success; !0 == failure
 * note: a CM shell implementation need implement only one of
 *       SendHeaderBatch or SendHeaderNoBatch; the shell's expectation is
 *       noted in the DRP Context Block passed to drpService.  a shell
 *       that registers itself as expecting batched headers need not
 *       implement a SetResponseHeader callback.
 */

/* UNIMPLEMENTED */

/**
 * SendHeaderNoBatch_fn
 * Send HTTP response status and header(s) to user agent (browser);
 *   headers will have been provided already via SetResponseHeader callback,
 *   and must be sent with this callback.
 * pShellData: shell-specific (request-specific) data object
 * iStatusCode: HTTP status code (e.g. 200)
 * iStatusMesg: optional status message for HTTP status line
 * iMesgLength: number of relevant bytes in iStatusMesg; test iMesgLength
 *              rather than iStatusMesg for presence of message
 * return: 0 == success; !0 == failure
 * note: a CM shell implementation need implement only one of
 *       SendHeaderBatch or SendHeaderNoBatch; the shell's expectation is
 *       noted in the DRP Context Block passed to drpService.  a shell
 *       that registers itself as expecting batched headers need not
 *       implement a SetResponseHeader callback.
 */

int SendHeaderNoBatch
    (void *pShellData, drpInt iStatusCode, const char *iStatusMesg,
     size_t iMesgLength) {
    httpTtrans *t = (httpTtrans *) pShellData;

    httpFset_status(t, iStatusCode, NULL);
    httpFstart_response(t);
    httpFsend_response(t);

    return 0;
}

/**
 * SetResponseHeader_fn
 * Set a single HTTP response header name-value pair, to be sent to the agent
 *   when SendHeaderNoBatch is invoked.
 * pShellData: shell-specific (request-specific) data object
 * iHeaderName: name of response header (ascii, null-terminated)
 * iHeaderValue: value of response header (ascii, null-terminated)
 * return: 0 == success; !0 == failure
 * note: a shell that registers itself as expecting batched headers need not
 *       implement a SetResponseHeader callback.
 */

int SetResponseHeader
    (void *pShellData, const growBuffer * iHeaderName,
     const growBuffer * iHeaderValue) {
    httpTtrans *t = (httpTtrans *) pShellData;

    /* strFtolower(iHeaderName->buffer); */

    /* remove in case we already have it */
    lstFset_delete_key(t->res_hdrs, iHeaderName->buffer);
    lstFset_add(t->res_hdrs, iHeaderName->buffer, iHeaderValue->buffer);

    return 0;
}

/**
 * cmldrpInitialize
 * Initializes the CMLDRP subsystem and initiates the first attempt to
 *   obtain DRP server information from a CMLDRP server (in the multiprocess
 *   case) or starts the CMLDRP thread (in the multithreaded case).
 * iNumServers: number of CMLDRP server info blocks in iServers
 * iServers: array of CMLDRP_ServerInfo objects
 * iBindAddress: address to which CMLDRP subsystem should bind its socket
 *               (NULL: bind to any appropriate local address)
 * iHTTPPort: main service port of the host HTTP server (for purposes of
 *            identifying this CMLDRP client to the CMLDRP server).
 * iLogFunction: http-server-API-specific logging routine
 * iLogShellData: opaque shellData object to be passed to iLogFunction
 * return: 0 == success; !0 == failure
 */

/*
 * cmldrpInitialize(int iNumServers, CMLDRP_ServerInfo * iServers, const char *
 * iBindAddress, unsigned short iHTTPPort, LogMessage_fn iLogFunction, void *
 * iLogShellData) {
 */

int dynamoFinit(void *p, lstTset * o)
{
    CMLDRP_ServerInfo server;
    char *host = lstFset_get(o, "cmldrp_host");
    char *port = lstFset_get(o, "cmldrp_port");

    logFmsg(0, "mod/dynamo: Dynamo Connection Manager v.%s (module)",
            kCM_VersionNumber);
    logFmsg(0, "mod/dynamo: Copyright (c) 2003, Alex Leigh");
    logFmsg(0, "mod/dynamo: Copyright (c) 1997, Art Technology Group");

    if (host == NULL || port == NULL) {
        logFmsg(2,
                "mod/dynamo: cmldrp_host and cmldrp_port not configured! Exiting..");
        exit(5);
    }
    server.host = malloc(strlen(host) + 1);
    strcpy(server.host, host);
    server.port = atoi(port);

    logFmsg(0, "mod/dynamo: Using CMLDDRP server %s:%s", host, port);

    return cmldrpInitialize(1, &server, NULL, 8990, &logHook, NULL);
}

/**
 * DefinedLength_fn
 * Return the length of the shell-specific ascii representation of the
 *   indicated DRP defined string.
 * iDefined: DRP defined string String-ID.
 * return: length of shell's representation of iDefined; 0 if the shell has
 *         no representation for iDefined
 */
/*
 * typedef int (* DefinedLength_fn) (drpByte iDefined);
 */

int definedLength(void *shellData, drpByte iDefined)
{
    return 0;
}

/**
 * DefinedAscii_fn
 * Return the (null-terminated) shell-specific ascii representation of the
 *   indicated DRP defined string.
 * iDefined: DRP defined string String-ID.
 * return: shell's representation of iDefined; NULL if the shell has no
 *         representation for iDefined.
 */
/*
 * typedef const char * (* DefinedAscii_fn) (drpByte iDefined);
 */

int definedAscii(void *shellData, drpByte iDefined)
{
    return NULL;
}


int dynamoFsession(httpTtrans * t, lstTset * arg)
{
    char *path = lstFset_get(t->vars, "uri");
    char *session;

    session = strstr(path, ";$sessionid");

    if (session != NULL) {
        *session = '\0';        /* truncate the session */
        session++;
        lstFset_add(t->vars, "drp-session", session);
    }
    return STATUS_PROCEED;
}

int dynamoFservice(httpTtrans * trans, lstTset * arg)
{
    drpContextBlock *gClientData = NULL;
    int ret;

#ifdef DEBUG
    logFmsg(3, "mod/dynamo: dynamoFservice called.");
#endif

    tasFstat_counter_add("http_svc", 0, "dynamoFservice", 1);

    gClientData = (drpContextBlock *) malloc(sizeof(drpContextBlock));
    memset(gClientData, 0, sizeof(drpContextBlock));

    drpReset(gClientData);

    gClientData->getParameter = &GetParameter;
    gClientData->getNthHeader = &GetNthHeader;
    gClientData->readFromAgent = &ReadFromAgent;
    gClientData->writeToAgent = &WriteToAgent;
    gClientData->flushData = &FlushData;
    gClientData->closeData = &CloseData;
    gClientData->sendHeaderBatch = NULL;
    gClientData->sendHeaderNoBatch = &SendHeaderNoBatch;
    gClientData->setResponseHeader = &SetResponseHeader;
    gClientData->definedStringLength = &definedLength;
    gClientData->definedStringToAscii = &definedAscii;
    gClientData->logMessage = &logHook;

    gClientData->shellExpectsHeaderPairs = 1;   /* not batched */
    gClientData->shellData = (void *) trans;

    /* set request logging */
    /* gClientData->isLoggedRequest = 1; */

    /* set debugging */
    gClientData->debugEnabled = 1;

    /* Max DRP connect timeout */
    gClientData->maxConnectTimeout = 30;

    ret = drpService(gClientData);

    if (ret != 0) {
        httpFset_status(trans, 500, "drpService() failed.");
        httpFstart_response(trans);
        httpFsend_response(trans);
        httpFerror_page(trans, "drpService() failed.");
    }
    return STATUS_EXIT;
}
