/*++

   Copyright    (c)   2000    Microsoft Corporation

   Module Name :
     logging.cxx

   Abstract:
     Handle Logging

   Author:
     Anil Ruia (AnilR)              1-Jul-2000

   Environment:
     Win32 - User Mode

   Project:
     w3core.dll
--*/

#include "precomp.hxx"

CONTEXT_STATUS W3_STATE_LOG::DoWork(
    W3_MAIN_CONTEXT *pMainContext,
    DWORD,
    DWORD)
/*++

Routine Description:

    Log the request (and call the END_OF_REQUEST notification)

Arguments:

    pMainContext - W3_MAIN_CONTEXT representing an execution of the
                   state machine

Return Value:

    CONTEXT_STATUS_CONTINUE - if we should continue in state machine
    else stop executing the machine and free up the current thread

--*/
{
    BOOL            fUnused = FALSE;

    if (pMainContext->QueryNeedFinalDone())
    {
        //
        // Clear remaining chunks from handle/response state
        //

        pMainContext->QueryResponse()->Clear();

        //
        // End of request notifications happen BEFORE logging.
        //

        if ( pMainContext->IsNotificationNeeded( SF_NOTIFY_END_OF_REQUEST ) )
        {
            pMainContext->NotifyFilters( SF_NOTIFY_END_OF_REQUEST,
                                         NULL,
                                         &fUnused );
        }
        
        //
        // Clear any crud from the last write from END_OF_REQUEST filter
        //

        pMainContext->QueryResponse()->Clear();

        //
        // Do the final send
        //

        pMainContext->SendEntity(W3_FLAG_PAST_END_OF_REQ);
    }

    if (pMainContext->QueryDoCustomLogging())
    {
        if (FAILED(pMainContext->CollectLoggingData(FALSE)))
        {
            goto Exit;
        }

        pMainContext->QuerySite()->LogInformation(pMainContext->QueryLogContext());
    }

 Exit:
    return CONTEXT_STATUS_CONTINUE;
}


CONTEXT_STATUS W3_STATE_LOG::OnCompletion(
    W3_MAIN_CONTEXT *,
    DWORD,
    DWORD)
/*++

Routine Description:

    Completion for the W3_STATE_LOG state

Arguments:

    pMainContext - W3_MAIN_CONTEXT representing an execution of the
                   state machine

Return Value:

    CONTEXT_STATUS_CONTINUE - if we should continue in state machine
    else stop executing the machine and free up the current thread

--*/
{
    //
    // This code should never get hit
    //
    DBG_ASSERT(FALSE);

    return CONTEXT_STATUS_CONTINUE;
}


HRESULT W3_MAIN_CONTEXT::CollectLoggingData(BOOL fCollectForULLogging)
/*++

Routine Description:

    Routine which collects all the logging data

Arguments:

    fCollectForULLogging - whether the data is being collected for http.sys/custom logging

Return Value:

    HRESULT

--*/
{
    STACK_STRA(strVal, 64);
    STACK_STRU(strValW, 64);
    HRESULT hr = S_OK;
    DWORD dwLength;
    CHUNK_BUFFER * pHeaderBuffer;

    HTTP_LOG_FIELDS_DATA *pUlLogData = QueryUlLogData();
    W3_REQUEST *pRequest = QueryRequest();
    W3_RESPONSE *pResponse = QueryResponse();

    pHeaderBuffer = QueryHeaderBuffer();
    DBG_ASSERT( pHeaderBuffer != NULL );

    if (IsNotificationNeeded(SF_NOTIFY_LOG))
    {
        //
        // There is a SF_NOTIFY_LOG filter, collect the data for the
        // notification
        //
        HTTP_FILTER_LOG filtLog;
        ZeroMemory(&filtLog, sizeof filtLog);

        if (SUCCEEDED(hr = GetServerVariableRemoteHost(this, &strVal)))
        {
            dwLength = strVal.QueryCCH() + 1;
            if (FAILED(hr = pHeaderBuffer->AllocateSpace(
                                dwLength,
                                (PCHAR *)&filtLog.pszClientHostName)))
            {
                goto Exit;
            }
            strVal.CopyToBuffer((LPSTR)filtLog.pszClientHostName,
                                 &dwLength);
        }
        else
        {
            goto Exit;
        }

        if (SUCCEEDED(hr = GetServerVariableLogonUser(this, &strVal)))
        {
            dwLength = strVal.QueryCCH() + 1;
            if (FAILED(hr = pHeaderBuffer->AllocateSpace(
                                dwLength,
                                (PCHAR *)&filtLog.pszClientUserName)))
            {
                goto Exit;
            }
            strVal.CopyToBuffer((LPSTR)filtLog.pszClientUserName,
                                 &dwLength);
        }
        else
        {
            goto Exit;
        }

        if (SUCCEEDED(hr = GetServerVariableLocalAddr(this, &strVal)))
        {
            dwLength = strVal.QueryCCH() + 1;
            if (FAILED(hr = pHeaderBuffer->AllocateSpace(
                                dwLength,
                                (PCHAR *)&filtLog.pszServerName)))
            {
                goto Exit;
            }
            strVal.CopyToBuffer((LPSTR)filtLog.pszServerName,
                                 &dwLength);
        }
        else
        {
            goto Exit;
        }

        pRequest->QueryVerb((CHAR **)&filtLog.pszOperation, (USHORT *)&dwLength);

        if (FAILED(hr = pRequest->GetUrl(&strValW)) ||
            FAILED(hr = strVal.CopyWToUTF8(strValW)) ||
            FAILED(hr = pHeaderBuffer->AllocateSpace(
                                strVal.QueryStr(),
                                strVal.QueryCCH(),
                                (CHAR **)&filtLog.pszTarget)))
        {
            goto Exit;
        }

        //
        // If an ISAPI used HSE_APPEND_LOG_PARAMETER use it
        //
        if (_LogContext.m_strLogParam.IsEmpty())
        {
            if (FAILED(hr = pRequest->GetQueryStringA(&_LogContext.m_strLogParam)))
            {
                goto Exit;
            }
        }
        filtLog.pszParameters = _LogContext.m_strLogParam.QueryStr();

        filtLog.dwHttpStatus = pResponse->QueryStatusCode();
        
        if ( HRESULT_FACILITY( QueryErrorStatus() ) == FACILITY_WIN32 )
        {
            filtLog.dwWin32Status = WIN32_FROM_HRESULT( QueryErrorStatus() );
        }
        else
        {
            filtLog.dwWin32Status = QueryErrorStatus();
        }

        filtLog.dwBytesSent = _LogContext.m_dwBytesSent;
        filtLog.dwBytesRecvd = _LogContext.m_dwBytesRecvd;
        filtLog.msTimeForProcessing = GetTickCount() - _LogContext.m_msStartTickCount;

        NotifyFilters(SF_NOTIFY_LOG, &filtLog, NULL);

        //
        // The filter may have changed some the data, copy it back to our
        // logging structure
        //
        pUlLogData->ClientIp = (CHAR *)filtLog.pszClientHostName;
        pUlLogData->ClientIpLength = (USHORT) strlen(filtLog.pszClientHostName);

        if (FAILED(hr = strValW.CopyA(filtLog.pszClientUserName)) ||
            FAILED(hr = pHeaderBuffer->AllocateSpace(
                            strValW.QueryStr(),
                            strValW.QueryCCH(),
                            (PWSTR*) &pUlLogData->UserName)))
        {
            goto Exit;
        }
        pUlLogData->UserNameLength =
            (USHORT)(strValW.QueryCCH() * sizeof(WCHAR));

        pUlLogData->ServerIp = (CHAR *)filtLog.pszServerName;
        pUlLogData->ServerIpLength = (USHORT) strlen(filtLog.pszServerName);

        pUlLogData->Method = (CHAR *)filtLog.pszOperation;
        pUlLogData->MethodLength = (USHORT) strlen(filtLog.pszOperation);
        pUlLogData->MethodNum = (HTTP_VERB)METHOD_HASH::GetIndex((CHAR *)filtLog.pszOperation);        

        if (FAILED(hr = strValW.CopyA(filtLog.pszTarget)) ||
            FAILED(hr = pHeaderBuffer->AllocateSpace(
                            strValW.QueryStr(),
                            strValW.QueryCCH(),
                            (PWSTR*) &pUlLogData->UriStem)))
        {
            goto Exit;
        }
        pUlLogData->UriStemLength =
            (USHORT)(strValW.QueryCCH() * sizeof(WCHAR));

        pUlLogData->UriQuery = (CHAR *)filtLog.pszParameters;
        pUlLogData->UriQueryLength = (USHORT) strlen(filtLog.pszParameters);

        pUlLogData->ProtocolStatus = (USHORT) filtLog.dwHttpStatus;
        pUlLogData->Win32Status = filtLog.dwWin32Status;
        _LogContext.m_dwBytesSent = filtLog.dwBytesSent;
        _LogContext.m_dwBytesRecvd = filtLog.dwBytesRecvd;
        _LogContext.m_msProcessingTime = filtLog.msTimeForProcessing;
    }
    else
    {
        //
        // No filter, just get the logging data
        //
        if (SUCCEEDED(hr = GetServerVariableRemoteHost(this, &strVal)))
        {
            if (FAILED(hr = pHeaderBuffer->AllocateSpace(
                                strVal.QueryStr(),
                                strVal.QueryCCH(),
                                (PSTR*) &pUlLogData->ClientIp)))
            {
                goto Exit;
            }
            pUlLogData->ClientIpLength = (USHORT)strVal.QueryCCH();
        }
        else
        {
            goto Exit;
        }

        if ( QueryUserContext() == NULL )
        {
            if (FAILED(hr = pRequest->GetRequestUserName(&strVal)))
            {
                goto Exit;
            }

            if (strVal.IsEmpty())
            {
                pUlLogData->UserName = L"";
                pUlLogData->UserNameLength = 0;
            }
            else
            {
                if (FAILED(hr = strValW.CopyA(strVal.QueryStr())) ||
                    FAILED(hr = pHeaderBuffer->AllocateSpace(
                                    strValW.QueryStr(),
                                    strValW.QueryCCH(),
                                    (PWSTR*) &pUlLogData->UserName)))
                {
                    goto Exit;
                }
                pUlLogData->UserNameLength = (USHORT)strValW.QueryCB();
            }
        }
        else
        {
            pUlLogData->UserName = QueryUserContext()->QueryUserName();
            if (pUlLogData->UserName)
            {
                pUlLogData->UserNameLength = (USHORT)wcslen(pUlLogData->UserName) * sizeof(WCHAR);
            }
        }

        if (SUCCEEDED(hr = GetServerVariableLocalAddr(this, &strVal)))
        {
            if (FAILED(hr = pHeaderBuffer->AllocateSpace(
                                strVal.QueryStr(),
                                strVal.QueryCCH(),
                                (PSTR*) &pUlLogData->ServerIp)))
            {
                goto Exit;
            }
            pUlLogData->ServerIpLength =
                (USHORT)strVal.QueryCCH();
        }
        else
        {
            goto Exit;
        }

        pRequest->QueryVerb((CHAR**) &pUlLogData->Method,
                            &pUlLogData->MethodLength);
        pUlLogData->MethodNum = pRequest->QueryVerbType();

        pRequest->QueryUrl((WCHAR**) &pUlLogData->UriStem,
                           &pUlLogData->UriStemLength);

        //
        // If an ISAPI used HSE_APPEND_LOG_PARAMETER use it
        //
        if (_LogContext.m_strLogParam.IsEmpty())
        {
            if (FAILED(hr = pRequest->GetQueryStringA(&_LogContext.m_strLogParam)))
            {
                goto Exit;
            }
        }
        pUlLogData->UriQuery = _LogContext.m_strLogParam.QueryStr();
        pUlLogData->UriQueryLength = (USHORT)_LogContext.m_strLogParam.QueryCCH();

        pUlLogData->ProtocolStatus = pResponse->QueryStatusCode();
        if ( HRESULT_FACILITY( QueryErrorStatus() ) == FACILITY_WIN32 )
        {
            pUlLogData->Win32Status = WIN32_FROM_HRESULT( QueryErrorStatus() );
        }
        else
        {
            pUlLogData->Win32Status = QueryErrorStatus();
        }

        if (!fCollectForULLogging)
        {
            _LogContext.m_msProcessingTime =
                GetTickCount() - _LogContext.m_msStartTickCount;
        }
    }

    //
    // Now get data unaffected by any SF_NOTIFY_LOG filter
    //
    
    HTTP_SUB_ERROR subError;
    pResponse->QuerySubError(&subError);
    pUlLogData->SubStatus = subError.mdSubError;

    pUlLogData->ServerPort = ntohs( pRequest->QueryLocalPort() );

    pUlLogData->ServiceName = QuerySite()->QueryName()->QueryStr();
    pUlLogData->ServiceNameLength = (USHORT)QuerySite()->QueryName()->QueryCCH();

    pUlLogData->ServerName = g_pW3Server->QueryComputerName();
    pUlLogData->ServerNameLength = g_pW3Server->QueryComputerNameLength();

    if (fCollectForULLogging)
    {
        pUlLogData->Host = pRequest->GetHeader(HttpHeaderHost,
                                               &pUlLogData->HostLength);

        pUlLogData->UserAgent = pRequest->GetHeader(HttpHeaderUserAgent,
                                                    &pUlLogData->UserAgentLength);
        pUlLogData->Cookie = pRequest->GetHeader(HttpHeaderCookie,
                                                 &pUlLogData->CookieLength);

        pUlLogData->Referrer = pRequest->GetHeader(HttpHeaderReferer,
                                                   &pUlLogData->ReferrerLength);
    }
    else
    {
        if (FAILED(hr = GetServerVariableHttpVersion(this,
                                        &_LogContext.m_strVersion)))
        {
            goto Exit;
        }

        if (QuerySite()->IsRequiredExtraLoggingFields())
        {
            //
            // If the custom logging module needs extra logging fields, get them
            //

            const MULTISZA *pmszExtraLoggingFields =
                QuerySite()->QueryExtraLoggingFields();
            LPSTR pszHeaderName = (LPSTR)pmszExtraLoggingFields->First();
            STACK_STRA( strHeaderName, 128);

            while (pszHeaderName != NULL)
            {
                hr = strHeaderName.Copy( pszHeaderName,
                                         (DWORD)strlen(pszHeaderName) - 1 );
                if (FAILED(hr))
                {
                    goto Exit;
                }

                hr = pRequest->GetHeader( strHeaderName,
                                          &strVal );
                if (FAILED(hr))
                {
                    if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_INDEX))
                    {
                        strVal.Reset();
                        hr = S_OK;
                    }
                    else
                    {
                        goto Exit;
                    }
                }

                if (!_LogContext.m_mszHTTPHeaders.Append(strVal.QueryStr()))
                {
                    hr = HRESULT_FROM_WIN32(GetLastError());
                    goto Exit;
                }

                pszHeaderName = (LPSTR)pmszExtraLoggingFields->Next(pszHeaderName);
            }
        }
    }

 Exit:
    return hr;
}