/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmWIXAccessControlList.h"

#include "cmCPackGenerator.h"

#include "cmSystemTools.h"

cmWIXAccessControlList::cmWIXAccessControlList(
    cmCPackLog* logger, cmInstalledFile const& installedFile,
    cmWIXSourceWriter& sourceWriter)
: Logger(logger)
, InstalledFile(installedFile)
, SourceWriter(sourceWriter)
{}

bool
cmWIXAccessControlList::Apply()
{
    std::vector<std::string> entries;
    this->InstalledFile.GetPropertyAsList("CPACK_WIX_ACL", entries);

    for(std::string const& entry : entries)
    {
        this->CreatePermissionElement(entry);
    }

    return true;
}

void
cmWIXAccessControlList::CreatePermissionElement(std::string const& entry)
{
    std::string::size_type pos = entry.find('=');
    if(pos == std::string::npos)
    {
        this->ReportError(entry, "Did not find mandatory '='");
        return;
    }

    std::string user_and_domain   = entry.substr(0, pos);
    std::string permission_string = entry.substr(pos + 1);

    pos = user_and_domain.find('@');
    std::string user;
    std::string domain;
    if(pos != std::string::npos)
    {
        user   = user_and_domain.substr(0, pos);
        domain = user_and_domain.substr(pos + 1);
    } else
    {
        user = user_and_domain;
    }

    std::vector<std::string> permissions =
        cmSystemTools::tokenize(permission_string, ",");

    this->SourceWriter.BeginElement("Permission");
    this->SourceWriter.AddAttribute("User", user);
    if(!domain.empty())
    {
        this->SourceWriter.AddAttribute("Domain", domain);
    }
    for(std::string const& permission : permissions)
    {
        this->EmitBooleanAttribute(entry,
                                   cmSystemTools::TrimWhitespace(permission));
    }
    this->SourceWriter.EndElement("Permission");
}

void
cmWIXAccessControlList::ReportError(std::string const& entry,
                                    std::string const& message)
{
    cmCPackLogger(cmCPackLog::LOG_ERROR, "Failed processing ACL entry '"
                                             << entry << "': " << message
                                             << std::endl);
}

bool
cmWIXAccessControlList::IsBooleanAttribute(std::string const& name)
{
    static const char* validAttributes[] = {
        /* clang-format needs this comment to break after the opening brace */
        "Append",
        "ChangePermission",
        "CreateChild",
        "CreateFile",
        "CreateLink",
        "CreateSubkeys",
        "Delete",
        "DeleteChild",
        "EnumerateSubkeys",
        "Execute",
        "FileAllRights",
        "GenericAll",
        "GenericExecute",
        "GenericRead",
        "GenericWrite",
        "Notify",
        "Read",
        "ReadAttributes",
        "ReadExtendedAttributes",
        "ReadPermission",
        "SpecificRightsAll",
        "Synchronize",
        "TakeOwnership",
        "Traverse",
        "Write",
        "WriteAttributes",
        "WriteExtendedAttributes",
        0
    };

    size_t i = 0;
    while(validAttributes[i])
    {
        if(name == validAttributes[i++])
            return true;
    }

    return false;
}

void
cmWIXAccessControlList::EmitBooleanAttribute(std::string const& entry,
                                             std::string const& name)
{
    if(!this->IsBooleanAttribute(name))
    {
        std::ostringstream message;
        message << "Unknown boolean attribute '" << name << "'";
        this->ReportError(entry, message.str());
    }

    this->SourceWriter.AddAttribute(name, "yes");
}
