/*++

Copyright (c) 2000  Microsoft Corporation

Module Name:

    comprogid.cpp

Abstract:

    Activation context section contributor for COM progid mapping.

Author:

    Michael J. Grier (MGrier) 23-Feb-2000

Revision History:

--*/

#include "stdinc.h"
#include <windows.h>
#include "sxsp.h"

DECLARE_STD_ATTRIBUTE_NAME_DESCRIPTOR(clsid);
DECLARE_STD_ATTRIBUTE_NAME_DESCRIPTOR(progid);
DECLARE_STD_ATTRIBUTE_NAME_DESCRIPTOR(id);

#define STRING_AND_LENGTH(x) x, NUMBER_OF(x)-1

BOOL
SxspComProgIdRedirectionStringSectionGenerationCallback(
    PVOID Context,
    ULONG Reason,
    PVOID CallbackData
    );

typedef struct _COM_PROGID_GLOBAL_CONTEXT *PCOM_PROGID_GLOBAL_CONTEXT;
typedef struct _COM_PROGID_SERVER_CONTEXT *PCOM_PROGID_SERVER_CONTEXT;

typedef struct _COM_PROGID_GLOBAL_CONTEXT
{
    // Temporary holding buffer for the configured CLSID until the first COM progid entry is
    // found, at which time a COM_PROGID_SERVER_CONTEXT is allocated and the filename moved to it.
    GUID m_ConfiguredClsid;
    PCOM_PROGID_SERVER_CONTEXT m_ServerContextListHead;
    ULONG m_ServerContextListCount;
} COM_PROGID_GLOBAL_CONTEXT;

typedef struct _COM_PROGID_SERVER_CONTEXT
{
    PCOM_PROGID_SERVER_CONTEXT m_Next;
    GUID m_ConfiguredClsid;
    LONG m_Offset; // populated during section generation
} COM_PROGID_SERVER_CONTEXT;

VOID
SxspComProgIdRedirectionContributorCallback(
    PACTCTXCTB_CALLBACK_DATA Data
    )
{
    FN_TRACE();

    PSTRING_SECTION_GENERATION_CONTEXT SSGenContext = (PSTRING_SECTION_GENERATION_CONTEXT) Data->Header.ActCtxGenContext;
    PCOM_PROGID_GLOBAL_CONTEXT ComGlobalContext = NULL;

    if (SSGenContext != NULL)
        ComGlobalContext = (PCOM_PROGID_GLOBAL_CONTEXT) ::SxsGetStringSectionGenerationContextCallbackContext(SSGenContext);

    switch (Data->Header.Reason)
    {
    case ACTCTXCTB_CBREASON_ACTCTXGENBEGINNING:
        Data->GenBeginning.Success = FALSE;

        INTERNAL_ERROR_CHECK(ComGlobalContext == NULL);
        INTERNAL_ERROR_CHECK(SSGenContext == NULL);

        IFALLOCFAILED_EXIT(ComGlobalContext = new COM_PROGID_GLOBAL_CONTEXT);

        ComGlobalContext->m_ConfiguredClsid = GUID_NULL;
        ComGlobalContext->m_ServerContextListHead = NULL;
        ComGlobalContext->m_ServerContextListCount = 0;

        if (!::SxsInitStringSectionGenerationContext(
                &SSGenContext,
                ACTIVATION_CONTEXT_DATA_COM_PROGID_REDIRECTION_FORMAT_WHISTLER,
                TRUE,
                &::SxspComProgIdRedirectionStringSectionGenerationCallback,
                ComGlobalContext))
        {
            FUSION_DELETE_SINGLETON(ComGlobalContext);
            goto Exit;
        }

        Data->Header.ActCtxGenContext = SSGenContext;

        Data->GenBeginning.Success = TRUE;

        break;

    case ACTCTXCTB_CBREASON_ACTCTXGENENDED:
        {
            PCOM_PROGID_SERVER_CONTEXT ServerContext;
            ULONG ContextCount;

            if (ComGlobalContext != NULL)
            {
                if (SSGenContext != NULL)
                    ::SxsDestroyStringSectionGenerationContext(SSGenContext);

                ServerContext = ComGlobalContext->m_ServerContextListHead;

                ContextCount = 0;

                while (ServerContext != NULL)
                {
                    PCOM_PROGID_SERVER_CONTEXT NextServerContext = ServerContext->m_Next;

                    FUSION_DELETE_SINGLETON(ServerContext);
                    ServerContext = NextServerContext;

                    if (ContextCount++ > ComGlobalContext->m_ServerContextListCount)
                        break;
                }

                // If this assert fires, we ran out of entries in the list before we hit as many
                // server contexts as the list thinks there are.
                ASSERT(ContextCount == ComGlobalContext->m_ServerContextListCount);

                // If this assert fires, we seem to have more entries in the list than the
                // list count indicates.
                ASSERT(ServerContext == NULL);

                FUSION_DELETE_SINGLETON(ComGlobalContext);
            }

            break;
        }

    case ACTCTXCTB_CBREASON_ALLPARSINGDONE:
        Data->AllParsingDone.Success = FALSE;
        if (SSGenContext != NULL)
            IFW32FALSE_EXIT(::SxsDoneModifyingStringSectionGenerationContext(SSGenContext));
        Data->AllParsingDone.Success = TRUE;
        break;

    case ACTCTXCTB_CBREASON_PCDATAPARSED:
        {
            Data->PCDATAParsed.Success = FALSE;

            ULONG MappedValue = 0;
            bool fFound = false;

            enum MappedValues
            {
                eAssemblyFileComclassProgid = 1
            };

            static const ELEMENT_PATH_MAP_ENTRY s_rgEntries[] =
            {
                { 4, STRING_AND_LENGTH(L"urn:schemas-microsoft-com:asm.v1^assembly!urn:schemas-microsoft-com:asm.v1^file!urn:schemas-microsoft-com:asm.v1^comClass!urn:schemas-microsoft-com:asm.v1^progid"), eAssemblyFileComclassProgid },
                { 3, STRING_AND_LENGTH(L"urn:schemas-microsoft-com:asm.v1^assembly!urn:schemas-microsoft-com:asm.v1^clrClass!urn:schemas-microsoft-com:asm.v1^progid"), eAssemblyFileComclassProgid }
            };

            IFW32FALSE_EXIT(
                ::SxspProcessElementPathMap(
                    Data->PCDATAParsed.ParseContext,
                    s_rgEntries,
                    NUMBER_OF(s_rgEntries),
                    MappedValue,
                    fFound));;

            if (fFound)
            {
                switch (MappedValue)
                {
                default:
                    INTERNAL_ERROR2_ACTION(MappedValue, "Invalid mapped value returned from SxspProcessElementPathMap()");

                case eAssemblyFileComclassProgid:
                    {
                        if (Data->Header.ManifestOperation == MANIFEST_OPERATION_GENERATE_ACTIVATION_CONTEXT)
                        {
                            PCOM_PROGID_SERVER_CONTEXT ServerContext = NULL;
                            INTERNAL_ERROR_CHECK(ComGlobalContext != NULL);
                            ServerContext = ComGlobalContext->m_ServerContextListHead;
                            INTERNAL_ERROR_CHECK(ServerContext != NULL);
                            IFW32FALSE_EXIT(
                                ::SxsAddStringToStringSectionGenerationContext(
                                    (PSTRING_SECTION_GENERATION_CONTEXT) Data->PCDATAParsed.Header.ActCtxGenContext,
                                    Data->PCDATAParsed.Text,
                                    Data->PCDATAParsed.TextCch,
                                    ServerContext,
                                    Data->PCDATAParsed.AssemblyContext->AssemblyRosterIndex,
                                    ERROR_SXS_DUPLICATE_PROGID));
                        }

                        break;
                    }
                }
            }

            Data->PCDATAParsed.Success = TRUE;

            break;
        }

    case ACTCTXCTB_CBREASON_ELEMENTPARSED:
        {
            Data->ElementParsed.Success = FALSE;

            ULONG MappedValue = 0;
            bool fFound = false;

            enum MappedValues
            {
                eAssemblyFileComclass = 1
            };

            static const ELEMENT_PATH_MAP_ENTRY s_rgEntries[] =
            {
                { 3, STRING_AND_LENGTH(L"urn:schemas-microsoft-com:asm.v1^assembly!urn:schemas-microsoft-com:asm.v1^file!urn:schemas-microsoft-com:asm.v1^comClass"), eAssemblyFileComclass },
                { 2, STRING_AND_LENGTH(L"urn:schemas-microsoft-com:asm.v1^assembly!urn:schemas-microsoft-com:asm.v1^clrClass"), eAssemblyFileComclass }
            };

            IFW32FALSE_EXIT(
                ::SxspProcessElementPathMap(
                    Data->ElementParsed.ParseContext,
                    s_rgEntries,
                    NUMBER_OF(s_rgEntries),
                    MappedValue,
                    fFound));

            if (fFound)
            {
                switch (MappedValue)
                {
                default:
                    INTERNAL_ERROR2_ACTION(MappedValue, "Invalid mapped value returned from SxspProcessElementPathMap()");

                case eAssemblyFileComclass:
                    {
                        bool fProgIdFound = false;
                        bool fFound = false;
                        SIZE_T cb;
                        CSmallStringBuffer VersionIndependentComClassIdBuffer;
                        PCOM_PROGID_SERVER_CONTEXT ServerContext = NULL;
                        CSmallStringBuffer ProgIdBuffer;
                        GUID ReferenceClsid, ConfiguredClsid, ImplementedClsid;

                        INTERNAL_ERROR_CHECK2(
                            ComGlobalContext != NULL,
                            "COM progid global context NULL while processing comClass tag");

                        IFW32FALSE_EXIT(
                            ::SxspGetAttributeValue(
                                SXSP_GET_ATTRIBUTE_VALUE_FLAG_REQUIRED_ATTRIBUTE,
                                &s_AttributeName_clsid,
                                &Data->ElementParsed,
                                fFound,
                                sizeof(VersionIndependentComClassIdBuffer),
                                &VersionIndependentComClassIdBuffer,
                                cb,
                                NULL,
                                0));

                        INTERNAL_ERROR_CHECK(fFound);

                        IFW32FALSE_EXIT(
                            ::SxspParseGUID(
                                VersionIndependentComClassIdBuffer,
                                VersionIndependentComClassIdBuffer.Cch(),
                                ReferenceClsid));

                        IFW32FALSE_EXIT(
                            ::SxspGetAttributeValue(
                                0,
                                &s_AttributeName_progid,
                                &Data->ElementParsed,
                                fProgIdFound,
                                sizeof(ProgIdBuffer),
                                &ProgIdBuffer,
                                cb,
                                NULL,
                                0));

                        // That was sufficient we are not generating an activation context,
                        // or if there's no progid= attribute on the element.
                        if (fProgIdFound && (Data->Header.ManifestOperation == MANIFEST_OPERATION_GENERATE_ACTIVATION_CONTEXT))
                        {
                            IFW32FALSE_EXIT(Data->Header.ClsidMappingContext->Map->MapReferenceClsidToConfiguredClsid(
                                        &ReferenceClsid,
                                        Data->ElementParsed.AssemblyContext,
                                        &ConfiguredClsid,
                                        &ImplementedClsid));

                            IFALLOCFAILED_EXIT(ServerContext = new COM_PROGID_SERVER_CONTEXT);

                            ServerContext->m_ConfiguredClsid = ConfiguredClsid;
                            ServerContext->m_Offset = 0;
                            ServerContext->m_Next = ComGlobalContext->m_ServerContextListHead;
                            ComGlobalContext->m_ServerContextListHead = ServerContext;
                            ComGlobalContext->m_ServerContextListCount++;

                            IFW32FALSE_EXIT(::SxsAddStringToStringSectionGenerationContext(
                                        (PSTRING_SECTION_GENERATION_CONTEXT) Data->ElementParsed.Header.ActCtxGenContext,
                                        ProgIdBuffer,
                                        ProgIdBuffer.Cch(),
                                        ServerContext,
                                        Data->ElementParsed.AssemblyContext->AssemblyRosterIndex,
                                        ERROR_SXS_DUPLICATE_PROGID));
                        }

                        break;
                    }

                }
            }

            Data->ElementParsed.Success = TRUE;

            break;
        }

    case ACTCTXCTB_CBREASON_GETSECTIONSIZE:
        Data->GetSectionSize.Success = FALSE;

        // Someone shouldn't be asking for the section size if we
        // are generating an activation context.
        // These two asserts should be equivalent...
        INTERNAL_ERROR_CHECK(Data->Header.ManifestOperation == MANIFEST_OPERATION_GENERATE_ACTIVATION_CONTEXT);
        INTERNAL_ERROR_CHECK(SSGenContext != NULL);

        IFW32FALSE_EXIT(
            ::SxsGetStringSectionGenerationContextSectionSize(
                SSGenContext,
                &Data->GetSectionSize.SectionSize));

        Data->GetSectionSize.Success = TRUE;

        break;

    case ACTCTXCTB_CBREASON_GETSECTIONDATA:
        Data->GetSectionData.Success = FALSE;

        INTERNAL_ERROR_CHECK(SSGenContext != NULL);
        INTERNAL_ERROR_CHECK(Data->Header.ManifestOperation == MANIFEST_OPERATION_GENERATE_ACTIVATION_CONTEXT);

        IFW32FALSE_EXIT(
            ::SxsGetStringSectionGenerationContextSectionData(
                SSGenContext,
                Data->GetSectionData.SectionSize,
                Data->GetSectionData.SectionDataStart,
                NULL));

        Data->GetSectionData.Success = TRUE;
        break;
    }
Exit:
    ;
}

BOOL
SxspComProgIdRedirectionStringSectionGenerationCallback(
    PVOID Context,
    ULONG Reason,
    PVOID CallbackData
    )
{
    BOOL fSuccess = FALSE;

    FN_TRACE_WIN32(fSuccess);

    PCOM_PROGID_GLOBAL_CONTEXT GlobalContext = (PCOM_PROGID_GLOBAL_CONTEXT) Context;

    switch (Reason)
    {
    default:
        INTERNAL_ERROR_CHECK(FALSE);
        goto Exit; // never hit this line, INTERNAL_ERROR_CHECK would "goto Exit"

    case STRING_SECTION_GENERATION_CONTEXT_CALLBACK_REASON_ENTRYDELETED:
        // do nothing;
        break;

    case STRING_SECTION_GENERATION_CONTEXT_CALLBACK_REASON_GETUSERDATASIZE:
        {
            PSTRING_SECTION_GENERATION_CONTEXT_CBDATA_GETUSERDATASIZE CBData = (PSTRING_SECTION_GENERATION_CONTEXT_CBDATA_GETUSERDATASIZE) CallbackData;
            CBData->DataSize = sizeof(GUID) * GlobalContext->m_ServerContextListCount;

            break;
        }

    case STRING_SECTION_GENERATION_CONTEXT_CALLBACK_REASON_GETUSERDATA:
        {
            PSTRING_SECTION_GENERATION_CONTEXT_CBDATA_GETUSERDATA CBData = (PSTRING_SECTION_GENERATION_CONTEXT_CBDATA_GETUSERDATA) CallbackData;
            SIZE_T BytesWritten = 0;
            SIZE_T BytesLeft = CBData->BufferSize;
            GUID *Cursor = (GUID *) CBData->Buffer;
            PCOM_PROGID_SERVER_CONTEXT ServerContext = GlobalContext->m_ServerContextListHead;

            INTERNAL_ERROR_CHECK2(
                BytesLeft >= (sizeof(GUID) * GlobalContext->m_ServerContextListCount),
                "progid section generation ran out of buffer storing configured clsids");

            BytesWritten += (sizeof(GUID) * GlobalContext->m_ServerContextListCount);
            BytesLeft -= (sizeof(GUID) * GlobalContext->m_ServerContextListCount);

            while (ServerContext != NULL)
            {
                ServerContext->m_Offset = static_cast<LONG>(((LONG_PTR) Cursor) - ((LONG_PTR) CBData->SectionHeader));
                *Cursor++ = ServerContext->m_ConfiguredClsid;
                ServerContext = ServerContext->m_Next;
            }

            CBData->BytesWritten = BytesWritten;

            break;
        }

    case STRING_SECTION_GENERATION_CONTEXT_CALLBACK_REASON_GETDATASIZE:
        {
            PSTRING_SECTION_GENERATION_CONTEXT_CBDATA_GETDATASIZE CBData = (PSTRING_SECTION_GENERATION_CONTEXT_CBDATA_GETDATASIZE) CallbackData;
            CBData->DataSize = sizeof(ACTIVATION_CONTEXT_DATA_COM_PROGID_REDIRECTION);
            break;
        }

    case STRING_SECTION_GENERATION_CONTEXT_CALLBACK_REASON_GETDATA:
        {
            PSTRING_SECTION_GENERATION_CONTEXT_CBDATA_GETDATA CBData = (PSTRING_SECTION_GENERATION_CONTEXT_CBDATA_GETDATA) CallbackData;
            PACTIVATION_CONTEXT_DATA_COM_PROGID_REDIRECTION Info;
            PCOM_PROGID_SERVER_CONTEXT ServerContext = (PCOM_PROGID_SERVER_CONTEXT) CBData->DataContext;

            SIZE_T BytesLeft = CBData->BufferSize;
            SIZE_T BytesWritten = 0;

            Info = (PACTIVATION_CONTEXT_DATA_COM_PROGID_REDIRECTION) CBData->Buffer;

            if (BytesLeft < sizeof(ACTIVATION_CONTEXT_DATA_COM_PROGID_REDIRECTION))
            {
                ::FusionpSetLastWin32Error(ERROR_INSUFFICIENT_BUFFER);
                goto Exit;
            }

            BytesWritten += sizeof(ACTIVATION_CONTEXT_DATA_COM_PROGID_REDIRECTION);
            BytesLeft -= sizeof(ACTIVATION_CONTEXT_DATA_COM_PROGID_REDIRECTION);

            Info->Size = sizeof(ACTIVATION_CONTEXT_DATA_COM_PROGID_REDIRECTION);
            Info->Flags = 0;
            Info->ConfiguredClsidOffset = ServerContext->m_Offset;

            CBData->BytesWritten = BytesWritten;

            break;
        }
    }

    fSuccess = TRUE;
Exit:
    return fSuccess;
}